/*-------------------------------------------------------------------------
 *
 * clauses.c
 *	  操作资格子句的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/util/clauses.c
 *
 * HISTORY
 *	  AUTHOR			DATE			MAJOR EVENT
 *	  Andrew Yu			Nov 3, 1994		clause.c 和 clauses.c 合并
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/subscripting.h"
#include "nodes/supportnodes.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"

typedef struct
{
	ParamListInfo boundParams;
	PlannerInfo *root;
	List	   *active_fns;
	Node	   *case_val;
	bool		estimate;
} eval_const_expressions_context;

typedef struct
{
	int			nargs;
	List	   *args;
	int		   *usecounts;
} substitute_actual_parameters_context;

typedef struct
{
	int			nargs;
	List	   *args;
	int			sublevels_up;
} substitute_actual_srf_parameters_context;

typedef struct
{
	char	   *proname;
	char	   *prosrc;
} inline_error_callback_arg;

typedef struct
{
	char		max_hazard;		/* 到目前为止发现的最差的并行危害 */
	char		max_interesting;	/* 最差的值得关注的并行危害 */
	List	   *safe_param_ids; /* PARAM_EXEC 参数 ID 视为安全的 */
} max_parallel_hazard_context;

static bool fc_contain_agg_clause_walker(Node *fc_node, void *fc_context);
static bool fc_find_window_functions_walker(Node *fc_node, WindowFuncLists *fc_lists);
static bool fc_contain_subplans_walker(Node *fc_node, void *fc_context);
static bool fc_contain_mutable_functions_walker(Node *fc_node, void *fc_context);
static bool fc_contain_volatile_functions_walker(Node *fc_node, void *fc_context);
static bool fc_contain_volatile_functions_not_nextval_walker(Node *fc_node, void *fc_context);
static bool fc_max_parallel_hazard_walker(Node *fc_node,
									   max_parallel_hazard_context *fc_context);
static bool fc_contain_nonstrict_functions_walker(Node *fc_node, void *fc_context);
static bool fc_contain_exec_param_walker(Node *fc_node, List *fc_param_ids);
static bool fc_contain_context_dependent_node(Node *fc_clause);
static bool fc_contain_context_dependent_node_walker(Node *fc_node, int *fc_flags);
static bool fc_contain_leaked_vars_walker(Node *fc_node, void *fc_context);
static Relids fc_find_nonnullable_rels_walker(Node *fc_node, bool fc_top_level);
static List *fc_find_nonnullable_vars_walker(Node *fc_node, bool fc_top_level);
static bool fc_is_strict_saop(ScalarArrayOpExpr *fc_expr, bool fc_falseOK);
static bool fc_convert_saop_to_hashed_saop_walker(Node *fc_node, void *fc_context);
static Node *fc_eval_const_expressions_mutator(Node *fc_node,
											eval_const_expressions_context *fc_context);
static bool fc_contain_non_const_walker(Node *fc_node, void *fc_context);
static bool fc_ece_function_is_safe(Oid fc_funcid,
								 eval_const_expressions_context *fc_context);
static List *fc_simplify_or_arguments(List *fc_args,
								   eval_const_expressions_context *fc_context,
								   bool *fc_haveNull, bool *fc_forceTrue);
static List *fc_simplify_and_arguments(List *fc_args,
									eval_const_expressions_context *fc_context,
									bool *fc_haveNull, bool *fc_forceFalse);
static Node *fc_simplify_boolean_equality(Oid fc_opno, List *fc_args);
static Expr *fc_simplify_function(Oid fc_funcid,
							   Oid fc_result_type, int32 fc_result_typmod,
							   Oid fc_result_collid, Oid fc_input_collid, List **fc_args_p,
							   bool fc_funcvariadic, bool fc_process_args, bool fc_allow_non_const,
							   eval_const_expressions_context *fc_context);
static List *fc_reorder_function_arguments(List *fc_args, int fc_pronargs,
										HeapTuple fc_func_tuple);
static List *add_function_defaults(List *fc_args, int fc_pronargs,
								   HeapTuple fc_func_tuple);
static List *fc_fetch_function_defaults(HeapTuple fc_func_tuple);
static void fc_recheck_cast_function_args(List *fc_args, Oid fc_result_type,
									   Oid *fc_proargtypes, int fc_pronargs,
									   HeapTuple fc_func_tuple);
static Expr *fc_evaluate_function(Oid fc_funcid, Oid fc_result_type, int32 fc_result_typmod,
							   Oid fc_result_collid, Oid fc_input_collid, List *fc_args,
							   bool fc_funcvariadic,
							   HeapTuple fc_func_tuple,
							   eval_const_expressions_context *fc_context);
static Expr *fc_inline_function(Oid fc_funcid, Oid fc_result_type, Oid fc_result_collid,
							 Oid fc_input_collid, List *fc_args,
							 bool fc_funcvariadic,
							 HeapTuple fc_func_tuple,
							 eval_const_expressions_context *fc_context);
static Node *fc_substitute_actual_parameters(Node *fc_expr, int fc_nargs, List *fc_args,
										  int *fc_usecounts);
static Node *fc_substitute_actual_parameters_mutator(Node *fc_node,
												  substitute_actual_parameters_context *fc_context);
static void fc_sql_inline_error_callback(void *fc_arg);
static Query *fc_substitute_actual_srf_parameters(Query *fc_expr,
											   int fc_nargs, List *fc_args);
static Node *fc_substitute_actual_srf_parameters_mutator(Node *fc_node,
													  substitute_actual_srf_parameters_context *fc_context);
static bool fc_pull_paramids_walker(Node *fc_node, Bitmapset **fc_context);


/*****************************************************************************
 *		聚合函数子句操作
 *****************************************************************************/

/*
 * contain_agg_clause
 *	  在子句中递归查找 Aggref/GroupingFunc 节点。
 *
 *	  如果找到任何聚合，则返回 true。
 *
 * 这不会深入子查询，因此仅在将子链接减少为子计划之后或在已知没有子查询的上下文中使用。
 * 也不能有外部聚合引用。
 *
 * （如果您想要类似的东西，但能够处理子查询，请参见 rewriteManip.c 的 contain_aggs_of_level()。）
 */
bool contain_agg_clause(Node *fc_clause)
{
	return fc_contain_agg_clause_walker(fc_clause, NULL);
}

static bool fc_contain_agg_clause_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Aggref))
	{
		Assert(((Aggref *) fc_node)->agglevelsup == 0);
		return true;			/* 中止树遍历并返回 true */
	}
	if (IsA(fc_node, GroupingFunc))
	{
		Assert(((GroupingFunc *) fc_node)->agglevelsup == 0);
		return true;			/* 中止树遍历并返回 true */
	}
	Assert(!IsA(fc_node, SubLink));
	return expression_tree_walker(fc_node, fc_contain_agg_clause_walker, fc_context);
}

/*****************************************************************************
 *		窗口函数子句操作
 *****************************************************************************/

/*
 * contain_window_function
 *	  在子句中递归查找 WindowFunc 节点。
 *
 * 由于窗口函数没有级别字段，但是与当前查询级别硬连线的，因此这与 rewriteManip.c 的函数相同。
 */
bool contain_window_function(Node *fc_clause)
{
	return contain_windowfuncs(fc_clause);
}

/*
 * find_window_functions
 *	  定位表达式树中的所有 WindowFunc 节点，并按 winref ID 编号对它们进行组织。
 *
 * 调用者必须提供对树中预期的 winref ID 的上限。
 */
WindowFuncLists *
find_window_functions(Node *fc_clause, Index fc_maxWinRef)
{
	WindowFuncLists *fc_lists = palloc(sizeof(WindowFuncLists));

	fc_lists->numWindowFuncs = 0;
	fc_lists->maxWinRef = fc_maxWinRef;
	fc_lists->windowFuncs = (List **) palloc0((fc_maxWinRef + 1) * sizeof(List *));
	(void) fc_find_window_functions_walker(fc_clause, fc_lists);
	return fc_lists;
}

static bool fc_find_window_functions_walker(Node *fc_node, WindowFuncLists *fc_lists)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, WindowFunc))
	{
		WindowFunc *fc_wfunc = (WindowFunc *) fc_node;

		/* winref 是无符号的，因此单侧测试是可以的 */
		if (fc_wfunc->winref > fc_lists->maxWinRef)
			elog(ERROR, "WindowFunc contains out-of-range winref %u",
				 fc_wfunc->winref);
		/* 消除重复，以避免重复计算 */
		if (!list_member(fc_lists->windowFuncs[fc_wfunc->winref], fc_wfunc))
		{
			fc_lists->windowFuncs[fc_wfunc->winref] =
				lappend(fc_lists->windowFuncs[fc_wfunc->winref], fc_wfunc);
			fc_lists->numWindowFuncs++;
		}

		/*
		 * 我们假设解析器检查了参数或过滤子句中没有窗口函数。
		 * 因此，我们无需递归进入它们。
		 * （如果解析器或规划者在这一点上搞错了，执行器仍然会捕捉到；请参见 ExecInitExpr。）
		 */
		return false;
	}
	Assert(!IsA(fc_node, SubLink));
	return expression_tree_walker(fc_node, fc_find_window_functions_walker,
								  (void *) fc_lists);
}


/*****************************************************************************
 *		支持返回集合的表达式
 *****************************************************************************/


/*
 * expression_returns_set_rows
 *	  估算由集合返回的表达式返回的行数。
 *	  如果它不是集合返回的表达式，则结果为 1。
 *
 * 我们只应检查最顶层的函数或运算符；以前递归是合适的，但现在不再如此。
 * （即使在函数的输入中有更多的 SRF，它们的乘数也是单独计算的。）
 *
 * 注意：请保持与 nodes/nodeFuncs.c 中的 expression_returns_set() 同步。
 */
double expression_returns_set_rows(PlannerInfo *fc_root, Node *fc_clause)
{
	if (fc_clause == NULL)
		return 1.0;
	if (IsA(fc_clause, FuncExpr))
	{
		FuncExpr   *fc_expr = (FuncExpr *) fc_clause;

		if (fc_expr->funcretset)
			return clamp_row_est(get_function_rows(fc_root, fc_expr->funcid, fc_clause));
	}
	if (IsA(fc_clause, OpExpr))
	{
		OpExpr	   *fc_expr = (OpExpr *) fc_clause;

		if (fc_expr->opretset)
		{
			set_opfuncid(fc_expr);
			return clamp_row_est(get_function_rows(fc_root, fc_expr->opfuncid, fc_clause));
		}
	}
	return 1.0;
}


/*****************************************************************************
 *		子计划子句操作
 *****************************************************************************/

/*
 * contain_subplans
 *	  递归搜索子句中的子计划节点。
 *
 * 如果我们看到 SubLink 节点，我们将返回 true。这只有在
 * 表达式树尚未被 subselect.c 转换时才可能。我们不知道该节点是否会产生真正的子计划还是仅仅是 initplan，
 * 但我们采取保守的假设，即它将是一个子计划。
 *
 * 如果找到任何子计划，则返回 true。
 */
bool contain_subplans(Node *fc_clause)
{
	return fc_contain_subplans_walker(fc_clause, NULL);
}

static bool fc_contain_subplans_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, SubPlan) ||
		IsA(fc_node, AlternativeSubPlan) ||
		IsA(fc_node, SubLink))
		return true;			/* 中止树遍历并返回 true */
	return expression_tree_walker(fc_node, fc_contain_subplans_walker, fc_context);
}


/*****************************************************************************
 *		检查子句中的可变函数
 *****************************************************************************/

/*
 * contain_mutable_functions
 *	  递归搜索子句中的可变函数。
 *
 * 如果找到任何可变函数（或由可变函数实现的运算符），则返回 true。
 * 这个测试是必要的，以免我们错误地认为像“WHERE random() < 0.5”这样的表达式可以被视为常量条件。
 *
 * 这只会对经过表达式预处理的子句给出正确的答案。通常，计划外的调用者
 * 应该使用 contain_mutable_functions_after_planning()，原因如前所述。
 *
 * 我们将递归查看查询节点（即 SubLink 子选择）
 * 但不查看 SubPlans。有关 contain_volatile_functions() 的注释，请参见。
 */
bool contain_mutable_functions(Node *fc_clause)
{
	return fc_contain_mutable_functions_walker(fc_clause, NULL);
}

static bool fc_contain_mutable_functions_checker(Oid fc_func_id, void *fc_context)
{
	return (func_volatile(fc_func_id) != PROVOLATILE_IMMUTABLE);
}

static bool fc_contain_mutable_functions_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	/* 检查节点本身中的可变函数 */
	if (check_functions_in_node(fc_node, fc_contain_mutable_functions_checker,
								fc_context))
		return true;

	if (IsA(fc_node, SQLValueFunction))
	{
		/* SQLValueFunction 的所有变体都是稳定的 */
		return true;
	}

	if (IsA(fc_node, NextValueExpr))
	{
		/* NextValueExpr 是易变的 */
		return true;
	}

	/*
	 * 将 MinMaxExpr 视为不可变应该是安全的，因为它将
	 * 依赖于非交叉类型的 btree 比较函数，而这些函数应该
	 * 始终是不可变的。将 XmlExpr 视为不可变则更可疑，
	 * 而将 CoerceToDomain 视为不可变则完全危险。但是我们
	 * 在历史上这样做过，改变这一点可能会导致更多
	 * 问题，而不是解决它。实际上，如果您有一个非不可变
	 * 的域约束，无论如何都会痛苦。
	 */

	/* 递归检查参数 */
	if (IsA(fc_node, Query))
	{
		/* 递归进入子选择 */
		return query_tree_walker((Query *) fc_node,
								 fc_contain_mutable_functions_walker,
								 fc_context, 0);
	}
	return expression_tree_walker(fc_node, fc_contain_mutable_functions_walker,
								  fc_context);
}

/*
 * contain_mutable_functions_after_planning
 *	  测试给定的表达式是否包含可变函数。
 *
 * 这是一个用于 contain_mutable_functions() 的包装器，安全地用于
 * 从计划外使用。不同之处在于，它首先运行表达式
 * 通过 expression_planner()。我们需要这样做的两个主要原因如下：
 *
 * 首先，默认函数参数将被插入，这可能影响
 * 易变性（考虑“默认 now()”）。
 *
 * 其次，可内联函数将被内联，这可能允许我们
 * 断定该函数在标记为的情况下实际上比它标记的易变性低。
 * 例如，多态函数必须标记它们对任何输入类型具有的最易变
 * 行为，但一旦我们内联该函数，我们可能能够得出结论，
 * 对于我们正在处理的特定输入类型，它并不会那么易变。
 */
bool contain_mutable_functions_after_planning(Expr *fc_expr)
{
	/*******************************************************************************  
 *		我们在这里假设 expression_planner() 不会修改它的输入  
*******************************************************************************/  
	fc_expr = expression_planner(fc_expr);

	/* 现在我们可以搜索非不可变函数 */  
	return contain_mutable_functions((Node *) fc_expr);
}


/****************************************************************************  
 *		检查子句中是否有易变函数  
 ****************************************************************************/  

/*  
 * contain_volatile_functions  
 *	  在子句中递归搜索易变函数。  
 *  
 * 如果发现任何易变函数（或由易变函数实现的运算符），则返回真。这个测试可以防止，例如，  
 * 将易变表达式无效转换为 indexscan quals。  
 *  
 * 这个测试只对经过表达式预处理的子句给出正确答案。通常，计划外的调用者应使用  
 * contain_volatile_functions_after_planning()，原因在于此处。  
 *  
 * 我们将递归查看 Query 节点（即 SubLink 子选择），但不查看 SubPlans。这有点奇怪，但  
 * 是故意的。如果我们正在查看 SubLink，我们可能是在判断一个查询树变换是否安全，  
 * 而其中的子选择应该会影响这一点；例如，包含易变函数的子选择的重复是有害的。  
 * 然而，一旦我们到了有 SubPlans 的阶段，后续的规划无需考虑其中的易变性，因为  
 * 执行器不会根据易变性改变对 SubPlan 的评估规则。  
 *  
 * 对于某些节点类型，例如，RestrictInfo 和 PathTarget，我们缓存是否找到了任何  
 * 易变函数，并在针对该节点的未来检查中重用该值。所有关于确定缓存值应该设置为  
 * VOLATILITY_NOVOLATILE 还是 VOLATILITY_VOLATILE 的逻辑都属于这个函数。任何对这些节点  
 * 进行更改的代码，如果可能会改变此函数的结果，必须将缓存值重置为  
 * VOLATILITY_UNKNOWN。这允许该函数在下次调用时重新确定正确的值，以便我们在  
 * 将来需要重新确定节点是否包含任何易变函数时。  
 */  
bool contain_volatile_functions(Node *fc_clause)
{
	return fc_contain_volatile_functions_walker(fc_clause, NULL);
}

static bool fc_contain_volatile_functions_checker(Oid fc_func_id, void *fc_context)
{
	return (func_volatile(fc_func_id) == PROVOLATILE_VOLATILE);
}

static bool fc_contain_volatile_functions_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	/* 检查节点本身是否有易变函数 */  
	if (check_functions_in_node(fc_node, fc_contain_volatile_functions_checker,
								fc_context))
		return true;

	if (IsA(fc_node, NextValueExpr))
	{
		/* NextValueExpr 是易变的 */
		return true;
	}

	if (IsA(fc_node, RestrictInfo))
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) fc_node;

		/*  
		 * 对于 RestrictInfo，检查我们是否之前检查过它的易变性。如果是这样，我们可以  
		 * 直接使用缓存值，而不必再次检查。否则，检查它并缓存我们是否找到任何  
		 * 易变函数。  
		 */  
		if (fc_rinfo->has_volatile == VOLATILITY_NOVOLATILE)
			return false;
		else if (fc_rinfo->has_volatile == VOLATILITY_VOLATILE)
			return true;
		else
		{
			bool		fc_hasvolatile;

			fc_hasvolatile = fc_contain_volatile_functions_walker((Node *) fc_rinfo->clause,
															fc_context);
			if (fc_hasvolatile)
				fc_rinfo->has_volatile = VOLATILITY_VOLATILE;
			else
				fc_rinfo->has_volatile = VOLATILITY_NOVOLATILE;

			return fc_hasvolatile;
		}
	}

	if (IsA(fc_node, PathTarget))
	{
		PathTarget *fc_target = (PathTarget *) fc_node;

		/*  
		 * 我们对 PathTarget 的缓存与对 RestrictInfos 的缓存方式相同。  
		 */  
		if (fc_target->has_volatile_expr == VOLATILITY_NOVOLATILE)
			return false;
		else if (fc_target->has_volatile_expr == VOLATILITY_VOLATILE)
			return true;
		else
		{
			bool		fc_hasvolatile;

			fc_hasvolatile = fc_contain_volatile_functions_walker((Node *) fc_target->exprs,
															fc_context);

			if (fc_hasvolatile)
				fc_target->has_volatile_expr = VOLATILITY_VOLATILE;
			else
				fc_target->has_volatile_expr = VOLATILITY_NOVOLATILE;

			return fc_hasvolatile;
		}
	}

	/*  
	 * 请参阅 contain_mutable_functions_walker 中的说明，关于为什么我们将  
	 * MinMaxExpr、XmlExpr 和 CoerceToDomain 视为不可变，而 SQLValueFunction 是稳定的。  
	 * 因此，以上都不是这里的兴趣。  
	 */  

	/* 递归检查参数 */
	if (IsA(fc_node, Query))
	{
		/* 递归进入子选择 */
		return query_tree_walker((Query *) fc_node,
								 fc_contain_volatile_functions_walker,
								 fc_context, 0);
	}
	return expression_tree_walker(fc_node, fc_contain_volatile_functions_walker,
								  fc_context);
}

/*  
 * contain_volatile_functions_after_planning  
 *	  测试给定表达式是否包含易变函数。  
 *  
 * 这是一个包装器，用于 contain_volatile_functions()，可以安全地从计划外使用。  
 * 区别在于它首先将表达式传递给 expression_planner()。我们需要这样做的两个主要原因是：  
 *  
 * 首先，函数的默认参数将被插入，这可能会影响易变性（考虑 "default random()"）。  
 *  
 * 其次，内联函数将被内联，这可能允许我们得出该函数实际上比标记的更不易变。  
 * 例如，多态函数必须用它在任何输入类型上具有的最易变行为进行标记，但一旦我们内联  
 * 该函数，我们可能能够得出结论，它对于我们处理的特定输入类型并不是那么易变。  
 */  
bool contain_volatile_functions_after_planning(Expr *fc_expr)
{
	/*******************************************************************************  
 *		我们在这里假设 expression_planner() 不会修改它的输入  
*******************************************************************************/  
	fc_expr = expression_planner(fc_expr);

	/* 现在我们可以搜索易变函数 */  
	return contain_volatile_functions((Node *) fc_expr);
}

/*  
 * 用于 COPY 的 contain_volatile_functions() 的特殊版本：  
 * 忽略 nextval()，但正常对待所有其他函数。  
 */  
bool contain_volatile_functions_not_nextval(Node *fc_clause)
{
	return fc_contain_volatile_functions_not_nextval_walker(fc_clause, NULL);
}

static bool fc_contain_volatile_functions_not_nextval_checker(Oid fc_func_id, void *fc_context)
{
	return (fc_func_id != F_NEXTVAL &&
			func_volatile(fc_func_id) == PROVOLATILE_VOLATILE);
}

static bool fc_contain_volatile_functions_not_nextval_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	/* 检查节点本身是否有易变函数 */  
	if (check_functions_in_node(fc_node,
								fc_contain_volatile_functions_not_nextval_checker,
								fc_context))
		return true;

	/*  
	 * 请参阅 contain_mutable_functions_walker 中的说明，关于为什么我们将  
	 * MinMaxExpr、XmlExpr 和 CoerceToDomain 视为不可变，而 SQLValueFunction 是稳定的。  
	 * 因此，以上都不是这里的兴趣。此外，由于我们故意忽略 nextval()，因此我们  
	 * 也应该忽略 NextValueExpr。  
	 */  

	/* 递归检查参数 */
	if (IsA(fc_node, Query))
	{
		/* 递归进入子选择 */
		return query_tree_walker((Query *) fc_node,
								 fc_contain_volatile_functions_not_nextval_walker,
								 fc_context, 0);
	}
	return expression_tree_walker(fc_node,
								  fc_contain_volatile_functions_not_nextval_walker,
								  fc_context);
}


/****************************************************************************  
 *		检查查询是否有并行不安全和/或受限构造  
 ****************************************************************************/  

  
/*
 * max_parallel_hazard
 *		查找给定查询中最坏的并行危害级别
 *
 * 返回在给定的解析树中可以找到的最早的功能危害属性（列表中的最早项：
 * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE）。我们用这个来查找查询
 * 是否可以并行化。调用者还会将结果保存在 PlannerGlobal 中，以便在查询树的部分检查中
 * 快速处理后续检查，通常情况下，当一切都是 SAFE 时。
 */
char max_parallel_hazard(Query *fc_parse)
{
	max_parallel_hazard_context fc_context;

	fc_context.max_hazard = PROPARALLEL_SAFE;
	fc_context.max_interesting = PROPARALLEL_UNSAFE;
	fc_context.safe_param_ids = NIL;
	(void) fc_max_parallel_hazard_walker((Node *) fc_parse, &fc_context);
	return fc_context.max_hazard;
}

/*
 * is_parallel_safe
 *		检测给定表达式是否仅包含并行安全函数
 *
 * root->glob->maxParallelHazard 必须之前已经设置为对整个查询
 * max_parallel_hazard() 的结果。
 */
bool is_parallel_safe(PlannerInfo *fc_root, Node *fc_node)
{
	max_parallel_hazard_context fc_context;
	PlannerInfo *fc_proot;
	ListCell   *fc_l;

	/*
	 * 即使原始查询树中没有不安全的内容，我们也需要搜索表达式
	 * 如果在规划时生成了任何 PARAM_EXEC Params，因为这些是并行受限的，并且可能在
	 * 这个表达式中有一个。但是其他情况下我们不需要查看。
	 */
	if (fc_root->glob->maxParallelHazard == PROPARALLEL_SAFE &&
		fc_root->glob->paramExecTypes == NIL)
		return true;
	/* 否则使用 max_parallel_hazard 的搜索逻辑，但在 RESTRICTED 处停止 */
	fc_context.max_hazard = PROPARALLEL_SAFE;
	fc_context.max_interesting = PROPARALLEL_RESTRICTED;
	fc_context.safe_param_ids = NIL;

	/*
	 * 指向相同或父查询级别的参数被认为是并行安全的。其理念是我们在 Gather
	 * 或 Gather Merge 节点计算这样的参数，并将其值传递给工作进程。
	 */
	for (fc_proot = fc_root; fc_proot != NULL; fc_proot = fc_proot->parent_root)
	{
		foreach(fc_l, fc_proot->init_plans)
		{
			SubPlan    *fc_initsubplan = (SubPlan *) lfirst(fc_l);

			fc_context.safe_param_ids = list_concat(fc_context.safe_param_ids,
												 fc_initsubplan->setParam);
		}
	}

	return !fc_max_parallel_hazard_walker(fc_node, &fc_context);
}

/* 所有并行危害检查的核心逻辑 */
static bool fc_max_parallel_hazard_test(char fc_proparallel, max_parallel_hazard_context *fc_context)
{
	switch (fc_proparallel)
	{
		case PROPARALLEL_SAFE:
			/* 这里没什么好看的，继续前进 */
			break;
		case PROPARALLEL_RESTRICTED:
			/* 将 max_hazard 增加到 RESTRICTED */
			Assert(fc_context->max_hazard != PROPARALLEL_UNSAFE);
			fc_context->max_hazard = fc_proparallel;
			/* 如果我们不期待任何不安全函数则完成 */
			if (fc_context->max_interesting == fc_proparallel)
				return true;
			break;
		case PROPARALLEL_UNSAFE:
			fc_context->max_hazard = fc_proparallel;
			/* 在第一个不安全构造处总是完成 */
			return true;
		default:
			elog(ERROR, "unrecognized proparallel value \"%c\"", fc_proparallel);
			break;
	}
	return false;
}

/* check_functions_in_node 回调 */
static bool fc_max_parallel_hazard_checker(Oid fc_func_id, void *fc_context)
{
	return fc_max_parallel_hazard_test(func_parallel(fc_func_id),
									(max_parallel_hazard_context *) fc_context);
}

static bool fc_max_parallel_hazard_walker(Node *fc_node, max_parallel_hazard_context *fc_context)
{
	if (fc_node == NULL)
		return false;

	/* 检查节点自身的危险函数 */
	if (check_functions_in_node(fc_node, fc_max_parallel_hazard_checker,
								fc_context))
		return true;

	/*
	 * 将 MinMaxExpr 视为并行安全应该是可以的，因为 btree
	 * opclass 支持的函数通常是并行安全的。XmlExpr 稍微有些可疑，但我们
	 * 可能可以做到这一点。我们采取谨慎的态度，将 CoerceToDomain 视为
	 * 并行受限。（注意：原则上这是错误的，因为领域约束可能
	 * 包含并行不安全的函数；但有用的约束可能永远不会有这样的函数，假设
	 * 它们确实存在，将会严重妨碍并行查询在领域类型存在时的使用。）SQLValueFunction
	 * 在所有情况下都应该是安全的。NextValueExpr 是并行不安全的。
	 */
	if (IsA(fc_node, CoerceToDomain))
	{
		if (fc_max_parallel_hazard_test(PROPARALLEL_RESTRICTED, fc_context))
			return true;
	}

	else if (IsA(fc_node, NextValueExpr))
	{
		if (fc_max_parallel_hazard_test(PROPARALLEL_UNSAFE, fc_context))
			return true;
	}

	/*
	 * 将窗口函数视为并行受限，因为我们无法确定输入行排序是否完全
	 * 确定，如果不是，窗口函数的输出可能在工作进程之间有所不同。
	 * （在某些情况下，例如窗口框架按主键排序时，我们可以放宽
	 * 这一限制。但目前似乎不值得为此付出额外的努力。）
	 */
	else if (IsA(fc_node, WindowFunc))
	{
		if (fc_max_parallel_hazard_test(PROPARALLEL_RESTRICTED, fc_context))
			return true;
	}

	/*
	 * 作为调用者的记号便利，查看 RestrictInfo。
	 */
	else if (IsA(fc_node, RestrictInfo))
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) fc_node;

		return fc_max_parallel_hazard_walker((Node *) fc_rinfo->clause, fc_context);
	}

	/*
	 * 实际上在 max_interesting == restricted 扫描期间我们不应该看到
	 * SubLink，但如果看到，返回 true。
	 */
	else if (IsA(fc_node, SubLink))
	{
		if (fc_max_parallel_hazard_test(PROPARALLEL_RESTRICTED, fc_context))
			return true;
	}

	/*
	 * 只有并行安全的 SubPlans 能够被发送到工作进程。在 SubPlan 的
	 * testexpr 中，表示子计划输出列的 Params 可以视为并行安全的，因此在检查
	 * testexpr 时暂时将其 ID 添加到 safe_param_ids 列表中。
	 */
	else if (IsA(fc_node, SubPlan))
	{
		SubPlan    *fc_subplan = (SubPlan *) fc_node;
		List	   *fc_save_safe_param_ids;

		if (!fc_subplan->parallel_safe &&
			fc_max_parallel_hazard_test(PROPARALLEL_RESTRICTED, fc_context))
			return true;
		fc_save_safe_param_ids = fc_context->safe_param_ids;
		fc_context->safe_param_ids = list_concat_copy(fc_context->safe_param_ids,
												   fc_subplan->paramIds);
		if (fc_max_parallel_hazard_walker(fc_subplan->testexpr, fc_context))
			return true;		/* 无需恢复 safe_param_ids */
		list_free(fc_context->safe_param_ids);
		fc_context->safe_param_ids = fc_save_safe_param_ids;
		/* 我们也必须检查参数，但不需要特别处理 Param */
		if (fc_max_parallel_hazard_walker((Node *) fc_subplan->args, fc_context))
			return true;
		/* 不想正常递归，所以我们完成了 */
		return false;
	}

	/*
	 * 目前我们也无法将Params传递给工作进程，因此它们也是
	 * 限制并行的，除非它们是PARAM_EXTERN Params或在safe_param_ids中列出的
	 * PARAM_EXEC Params，意味着它们可以在工作进程中生成或由领导者计算，
	 * 然后其值可以传递给工作进程。
	 */
	else if (IsA(fc_node, Param))
	{
		Param	   *fc_param = (Param *) fc_node;

		if (fc_param->paramkind == PARAM_EXTERN)
			return false;

		if (fc_param->paramkind != PARAM_EXEC ||
			!list_member_int(fc_context->safe_param_ids, fc_param->paramid))
		{
			if (fc_max_parallel_hazard_test(PROPARALLEL_RESTRICTED, fc_context))
				return true;
		}
		return false;			/* 没有要递归到的内容 */
	}

	/*
	 * 当我们第一次在一个完全未计划的树上被调用时，我们必须
	 * 递归进入子查询，以寻找树中的任何并行不安全构造。
	 */
	else if (IsA(fc_node, Query))
	{
		Query	   *fc_query = (Query *) fc_node;

		/* SELECT FOR UPDATE/SHARE 必须被视为不安全 */
		if (fc_query->rowMarks != NULL)
		{
			fc_context->max_hazard = PROPARALLEL_UNSAFE;
			return true;
		}

		/* 递归进入子选择 */
		return query_tree_walker(fc_query,
								 fc_max_parallel_hazard_walker,
								 fc_context, 0);
	}

	/* 递归检查参数 */
	return expression_tree_walker(fc_node,
								  fc_max_parallel_hazard_walker,
								  fc_context);
}


/*****************************************************************************
 *		检查非严格函数的子句
 *****************************************************************************/

/*
 * contain_nonstrict_functions
 *	  在子句中递归搜索非严格函数。
 *
 * 如果找到任何非严格构造，则返回真——即，任何可能
 * 在NULL输入下产生非NULL输出的内容。
 *
 * 这里的想法是，调用者已经验证表达式包含
 * 一个或多个Var或Param节点（根据调用者的需要），并且
 * 现在希望证明如果这些输入中的任何一个是NULL，那么表达式结果将是NULL。
 * 如果我们返回false，那么证明成功。
 */
bool contain_nonstrict_functions(Node *fc_clause)
{
	return fc_contain_nonstrict_functions_walker(fc_clause, NULL);
}

static bool fc_contain_nonstrict_functions_checker(Oid fc_func_id, void *fc_context)
{
	return !func_strict(fc_func_id);
}

static bool fc_contain_nonstrict_functions_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Aggref))
	{
		/* 聚合函数可能在NULL输入时返回非NULL */
		return true;
	}
	if (IsA(fc_node, GroupingFunc))
	{
		/*
		 * GroupingFunc不评估其参数，因此必须
		 * 被视为非严格。
		 */
		return true;
	}
	if (IsA(fc_node, WindowFunc))
	{
		/* 窗口函数可能在NULL输入时返回非NULL */
		return true;
	}
	if (IsA(fc_node, SubscriptingRef))
	{
		SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_node;
		const SubscriptRoutines *fc_sbsroutines;

		/* 下标赋值始终被认为是非严格的 */
		if (fc_sbsref->refassgnexpr != NULL)
			return true;
		/* 否则我们必须查找下标支持方法 */
		fc_sbsroutines = getSubscriptingRoutines(fc_sbsref->refcontainertype, NULL);
		if (!(fc_sbsroutines && fc_sbsroutines->fetch_strict))
			return true;
		/* 否则继续检查参数 */
	}
	if (IsA(fc_node, DistinctExpr))
	{
		/* IS DISTINCT FROM 本质上是非严格的 */
		return true;
	}
	if (IsA(fc_node, NullIfExpr))
	{
		/* NULLIF 本质上是非严格的 */
		return true;
	}
	if (IsA(fc_node, BoolExpr))
	{
		BoolExpr   *fc_expr = (BoolExpr *) fc_node;

		switch (fc_expr->boolop)
		{
			case AND_EXPR:
			case OR_EXPR:
				/* AND，OR 本质上是非严格的 */
				return true;
			default:
				break;
		}
	}
	if (IsA(fc_node, SubLink))
	{
		/* 在某些情况下，子链接可能是严格的，但通常不是 */
		return true;
	}
	if (IsA(fc_node, SubPlan))
		return true;
	if (IsA(fc_node, AlternativeSubPlan))
		return true;
	if (IsA(fc_node, FieldStore))
		return true;
	if (IsA(fc_node, CoerceViaIO))
	{
		/*
		 * CoerceViaIO是严格的，无论I/O函数是否严格，
		 * 所以只需查看其参数；询问check_functions_in_node是
		 * 没有用的开销，可能会给出错误的答案。
		 */
		return fc_contain_nonstrict_functions_walker((Node *) ((CoerceViaIO *) fc_node)->arg,
												  fc_context);
	}
	if (IsA(fc_node, ArrayCoerceExpr))
	{
		/*
		 * ArrayCoerceExpr在数组级别上是严格的，无论
		 * 每个元素的表达式是什么；因此我们应该忽略elemexpr并
		 * 仅递归到arg。
		 */
		return fc_contain_nonstrict_functions_walker((Node *) ((ArrayCoerceExpr *) fc_node)->arg,
												  fc_context);
	}
	if (IsA(fc_node, CaseExpr))
		return true;
	if (IsA(fc_node, ArrayExpr))
		return true;
	if (IsA(fc_node, RowExpr))
		return true;
	if (IsA(fc_node, RowCompareExpr))
		return true;
	if (IsA(fc_node, CoalesceExpr))
		return true;
	if (IsA(fc_node, MinMaxExpr))
		return true;
	if (IsA(fc_node, XmlExpr))
		return true;
	if (IsA(fc_node, NullTest))
		return true;
	if (IsA(fc_node, BooleanTest))
		return true;

	/* 检查其他包含函数的节点 */
	if (check_functions_in_node(fc_node, fc_contain_nonstrict_functions_checker,
								fc_context))
		return true;

	return expression_tree_walker(fc_node, fc_contain_nonstrict_functions_walker,
								  fc_context);
}

/*****************************************************************************
 *		检查Params的子句
 *****************************************************************************/

/*
 * contain_exec_param
 *	  在子句中递归搜索PARAM_EXEC Params。
 *
 * 如果子句包含任何具有给定Param ID列表中出现的paramid的PARAM_EXEC Param，
 * 则返回真。 不会下降到子查询中！
 */
bool contain_exec_param(Node *fc_clause, List *fc_param_ids)
{
	return fc_contain_exec_param_walker(fc_clause, fc_param_ids);
}

static bool fc_contain_exec_param_walker(Node *fc_node, List *fc_param_ids)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Param))
	{
		Param	   *fc_p = (Param *) fc_node;

		if (fc_p->paramkind == PARAM_EXEC &&
			list_member_int(fc_param_ids, fc_p->paramid))
			return true;
	}
	return expression_tree_walker(fc_node, fc_contain_exec_param_walker, fc_param_ids);
}

/*****************************************************************************
 *		检查上下文相关节点的子句
 *****************************************************************************/

/*
 * contain_context_dependent_node
 *	  在子句中递归搜索上下文相关节点。
 *
 * CaseTestExpr节点必须直接出现在相应的CaseExpr中，
 * 而不是嵌套在另一个中，否则它们将看到错误的测试值。
 * 如果一个在SQL函数的参数中以“裸”形式出现，那么我们就不能内联
 * SQL函数，以免造成这种情况。对于在ArrayCoerceExpr的elemexpr中使用的
 * CaseTestExpr也是如此。
 *
 * 如果域CHECK表达式可以被内联进更大表达式中，那么CoerceToDomainValue也会有同样的问题，
 * 但目前这是不可能的。此后可能在未来允许，或者可能会发明其他类型
 * 具有类似问题的节点。因此给这个函数一个通用名称，并设置
 * 递归状态以允许多个标志位。
 */
static bool fc_contain_context_dependent_node(Node *fc_clause)
{
	int			fc_flags = 0;

	return fc_contain_context_dependent_node_walker(fc_clause, &fc_flags);
}

#define CCDN_CASETESTEXPR_OK	0x0001	/* 此处CaseTestExpr可以吗？ */

static bool fc_contain_context_dependent_node_walker(Node *fc_node, int *fc_flags)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, CaseTestExpr))
		return !(*fc_flags & CCDN_CASETESTEXPR_OK);
	else if (IsA(fc_node, CaseExpr))
	{
		CaseExpr   *fc_caseexpr = (CaseExpr *) fc_node;

		/*
		 * 如果这个CASE没有测试表达式，那么它不会创建
		 * CaseTestExpr应该出现的上下文，因此只需继续
		 * 并将其视为一个通用表达式节点。
		 */
		if (fc_caseexpr->arg)
		{
			int			fc_save_flags = *fc_flags;
			bool		fc_res;

			/*
			 * 注意：原则上，我们可以区分CASE构造的各种子部分，
			 * 并仅为其中一些设置标志位，因为我们只期待CaseTestExpr出现在
			 * CaseWhen节点的“expr”子树中。但这似乎并不值得任何额外代码。
			 * 如果在CASE的其他地方有任何裸的CaseTestExpr，那么已经出错了。
			 */
			*fc_flags |= CCDN_CASETESTEXPR_OK;
			fc_res = expression_tree_walker(fc_node,
										 fc_contain_context_dependent_node_walker,
										 (void *) fc_flags);
			*fc_flags = fc_save_flags;
			return fc_res;
		}
	}
	else if (IsA(fc_node, ArrayCoerceExpr))
	{
		ArrayCoerceExpr *fc_ac = (ArrayCoerceExpr *) fc_node;
		int			fc_save_flags;
		bool		fc_res;

		/* 检查数组表达式 */
		if (fc_contain_context_dependent_node_walker((Node *) fc_ac->arg, fc_flags))
			return true;

		/* 检查elemexpr，允许包含CaseTestExpr */
		fc_save_flags = *fc_flags;
		*fc_flags |= CCDN_CASETESTEXPR_OK;
		fc_res = fc_contain_context_dependent_node_walker((Node *) fc_ac->elemexpr,
													fc_flags);
		*fc_flags = fc_save_flags;
		return fc_res;
	}
	return expression_tree_walker(fc_node, fc_contain_context_dependent_node_walker,
								  (void *) fc_flags);
}


/*****************************************************************************
 *		  检查传递给非泄漏安全函数的变量的子句
 *****************************************************************************/

/*
 * contain_leaked_vars
 *		递归扫描一个子句以发现它是否包含任何变量
 *		节点（当前查询级别）作为参数传递给
 *		泄漏函数。
 *
 * 如果子句包含任何非泄漏安全函数，这些函数传递了
 * 当前查询级别的变量节点，并且可能因此泄漏
 * 数据，则返回真。这样的子句必须在任何低级安全屏障
 * 子句之后应用。
 */
bool contain_leaked_vars(Node *fc_clause)
{
	return fc_contain_leaked_vars_walker(fc_clause, NULL);
}

static bool fc_contain_leaked_vars_checker(Oid fc_func_id, void *fc_context)
{
	return !get_func_leakproof(fc_func_id);
}

static bool fc_contain_leaked_vars_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;

	switch (nodeTag(fc_node))
	{
		case T_Var:
		case T_Const:
		case T_Param:
		case T_ArrayExpr:
		case T_FieldSelect:
		case T_FieldStore:
		case T_NamedArgExpr:
		case T_BoolExpr:
		case T_RelabelType:
		case T_CollateExpr:
		case T_CaseExpr:
		case T_CaseTestExpr:
		case T_RowExpr:
		case T_SQLValueFunction:
		case T_NullTest:
		case T_BooleanTest:
		case T_NextValueExpr:
		case T_List:

			/*
			 * 我们知道这些节点类型不包含函数调用；但是
			 * 节点树中的某些更深层次的部分可能会包含。
			 */
			break;

		case T_FuncExpr:
		case T_OpExpr:
		case T_DistinctExpr:
		case T_NullIfExpr:
		case T_ScalarArrayOpExpr:
		case T_CoerceViaIO:
		case T_ArrayCoerceExpr:

			/*
			 * 如果节点包含一个泄漏的函数调用，并且它下面有任何变量
			 * ，则拒绝。
			 */
			if (check_functions_in_node(fc_node, fc_contain_leaked_vars_checker,
										fc_context) &&
				contain_var_clause(fc_node))
				return true;
			break;

		case T_SubscriptingRef:
			{
				SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_node;
				const SubscriptRoutines *fc_sbsroutines;

				/* 查阅下标支持方法信息 */
				fc_sbsroutines = getSubscriptingRoutines(fc_sbsref->refcontainertype,
													  NULL);
				if (!fc_sbsroutines ||
					!(fc_sbsref->refassgnexpr != NULL ?
					  fc_sbsroutines->store_leakproof :
					  fc_sbsroutines->fetch_leakproof))
				{
					/* 节点是泄漏的，因此如果包含变量则拒绝 */
					if (contain_var_clause(fc_node))
						return true;
				}
			}
			break;

		case T_RowCompareExpr:
			{
				/*
				 * 特别处理这个是值得的，因为泄漏的比较
				 * 函数只会影响一对行元素，而这对元素可能不包含变量，
				 * 而其他元素可能包含。
				 */
				RowCompareExpr *fc_rcexpr = (RowCompareExpr *) fc_node;
				ListCell   *fc_opid;
				ListCell   *fc_larg;
				ListCell   *fc_rarg;

				forthree(fc_opid, fc_rcexpr->opnos,
						 fc_larg, fc_rcexpr->largs,
						 fc_rarg, fc_rcexpr->rargs)
				{
					Oid			fc_funcid = get_opcode(lfirst_oid(fc_opid));

					if (!get_func_leakproof(fc_funcid) &&
						(contain_var_clause((Node *) lfirst(fc_larg)) ||
						 contain_var_clause((Node *) lfirst(fc_rarg))))
						return true;
				}
			}
			break;

		case T_MinMaxExpr:
			{
				/*
				 * 如果它调用的比较函数是无泄漏的，
				 * MinMaxExpr是无泄漏的。
				 */
				MinMaxExpr *fc_minmaxexpr = (MinMaxExpr *) fc_node;
				TypeCacheEntry *fc_typentry;
				bool		fc_leakproof;

				/* 查找数据类型的 btree 比较函数 */
				fc_typentry = lookup_type_cache(fc_minmaxexpr->minmaxtype,
											 TYPECACHE_CMP_PROC);
				if (OidIsValid(fc_typentry->cmp_proc))
					fc_leakproof = get_func_leakproof(fc_typentry->cmp_proc);
				else
				{
					/*
					 * 执行器会抛出错误，但是在这里我们只是
					 * 将缺失的函数视为泄漏。
					 */
					fc_leakproof = false;
				}

				if (!fc_leakproof &&
					contain_var_clause((Node *) fc_minmaxexpr->args))
					return true;
			}
			break;

		case T_CurrentOfExpr:

			/*
			 * WHERE CURRENT OF 不包含泄漏的函数调用。
			 * 此外，考虑到这一点是非泄漏的至关重要，
			 * 因为当存在 CURRENT OF 时，计划必须始终生成 TID 扫描 -- 参见 cost_tidscan。
			 */
			return false;

		default:

			/*
			 * 如果我们不识别节点标签，假设它可能是泄漏的。
			 * 这可以防止在某人添加新的能够调用函数的
			 * 节点类型时出现意外的安全漏洞。
			 */
			return true;
	}
	return expression_tree_walker(fc_node, fc_contain_leaked_vars_walker,
								  fc_context);
}

/*
 * find_nonnullable_rels
 *		确定给定子句强制非空的基本关系。
 *
 * 返回在子句中以这样的方式引用的所有 Relids 的集合，
 * 以至于如果这些 Relids 中的任何一个是全 NULL 行，
 * 子句不可能返回 TRUE。 （在保守的一方错误是可以的；
 * 因此此处的分析是简单化的。）
 *
 * 此处的语义与 contain_nonstrict_functions 有微妙的不同：
 * 该功能关心任意表达式的 NULL 结果，
 * 但这里我们假设输入是布尔表达式，
 * 并希望查看 NULL 输入是否能明显导致 FALSE 或 NULL 结果。
 * 我们期望表达式已被 AND/OR 扁平化并转换为隐式 AND 格式。
 *
 * 注意：此函数在很大程度上与 find_nonnullable_vars() 重复。
 * 不将该函数简化为对 find_nonnullable_vars() 的薄包装的原因
 * 是测试条件确实不同：
 * 像 "t1.v1 IS NOT NULL OR t1.v2 IS NOT NULL" 的子句
 * 并不能证明 v1 或 v2 不能为 NULL，
 * 但它确实证明了 t1 行整体不能为全 NULL。
 * 此外，PHV 的行为不同。
 *
 * 当扫描顶级 AND/OR 结构时，top_level 为真；
 * 在这里，显示结果为 FALSE 或 NULL 就足够了。
 * 当我们下降到 NOT 或严格函数以下时，top_level 为假：
 * 现在我们必须能够证明子表达式是 NULL。
 *
 * 我们在这里不使用 expression_tree_walker，
 * 因为我们不想通过许多类型的节点，仅通过我们能确保的严格节点进行遍历。
 */
Relids find_nonnullable_rels(Node *fc_clause)
{
	return fc_find_nonnullable_rels_walker(fc_clause, true);
}

static Relids fc_find_nonnullable_rels_walker(Node *fc_node, bool fc_top_level)
{
	Relids		fc_result = NULL;
	ListCell   *fc_l;

	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		if (fc_var->varlevelsup == 0)
			fc_result = bms_make_singleton(fc_var->varno);
	}
	else if (IsA(fc_node, List))
	{
		/*
		 * 在顶层，我们正在检查隐式 AND 列表：
		 * 如果任何一个分支产生 FALSE 或 NULL，
		 * 那么结果就是 FALSE 或 NULL。
		 * 如果不在顶层，我们正在检查严格函数的参数：
		 * 如果其中任何一个产生 NULL，则函数的结果必须是 NULL。
		 * 因此在这两个情况下，非空关系的集合是
		 * 在分支中找到的集合的并集，
		 * 并且我们将 top_level 标志未修改地传递下去。
		 */
		foreach(fc_l, (List *) fc_node)
		{
			fc_result = bms_join(fc_result,
							  fc_find_nonnullable_rels_walker(lfirst(fc_l),
														   fc_top_level));
		}
	}
	else if (IsA(fc_node, FuncExpr))
	{
		FuncExpr   *fc_expr = (FuncExpr *) fc_node;

		if (func_strict(fc_expr->funcid))
			fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->args, false);
	}
	else if (IsA(fc_node, OpExpr))
	{
		OpExpr	   *fc_expr = (OpExpr *) fc_node;

		set_opfuncid(fc_expr);
		if (func_strict(fc_expr->opfuncid))
			fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->args, false);
	}
	else if (IsA(fc_node, ScalarArrayOpExpr))
	{
		ScalarArrayOpExpr *fc_expr = (ScalarArrayOpExpr *) fc_node;

		if (fc_is_strict_saop(fc_expr, true))
			fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->args, false);
	}
	else if (IsA(fc_node, BoolExpr))
	{
		BoolExpr   *fc_expr = (BoolExpr *) fc_node;

		switch (fc_expr->boolop)
		{
			case AND_EXPR:
				/* 在顶层我们可以直接递归（到列表案例） */
				if (fc_top_level)
				{
					fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->args,
														  fc_top_level);
					break;
				}

				/*
				 * 在顶层以下，即使一个分支产生 NULL，
				 * 结果仍然可能是 FALSE（因此不是 NULL）。
				 * 然而，如果 *所有* 分支都产生 NULL，
				 * 则结果为 NULL，因此我们可以获得非空关系集合的交集，
				 * 就像对于 OR 一样。 转到共享代码。
				 */
				/* FALL THRU */
			case OR_EXPR:

				/*
				 * 如果所有分支都是严格的，OR 就是严格的，
				 * 因此我们可以为每个分支获取非空关系集合的交集。
				 * 这对顶层的两个值都有效。
				 */
				foreach(fc_l, fc_expr->args)
				{
					Relids		fc_subresult;

					fc_subresult = fc_find_nonnullable_rels_walker(lfirst(fc_l),
															 fc_top_level);
					if (fc_result == NULL) /* 第一个子结果？ */
						fc_result = fc_subresult;
					else
						fc_result = bms_int_members(fc_result, fc_subresult);

					/*
					 * 如果交集为空，我们可以停止查找。
					 * 这也证明了上面有关首次子结果的测试的合理性。
					 */
					if (bms_is_empty(fc_result))
						break;
				}
				break;
			case NOT_EXPR:
				/* NOT 如果其参数为 NULL，则返回 NULL */
				fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->args,
													  false);
				break;
			default:
				elog(ERROR, "unrecognized boolop: %d", (int) fc_expr->boolop);
				break;
		}
	}
	else if (IsA(fc_node, RelabelType))
	{
		RelabelType *fc_expr = (RelabelType *) fc_node;

		fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, CoerceViaIO))
	{
		/* 不清楚这是否有用，但没什么坏处 */
		CoerceViaIO *fc_expr = (CoerceViaIO *) fc_node;

		fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, ArrayCoerceExpr))
	{
		/* ArrayCoerceExpr 在数组级别是严格的；忽略 elemexpr */
		ArrayCoerceExpr *fc_expr = (ArrayCoerceExpr *) fc_node;

		fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, ConvertRowtypeExpr))
	{
		/* 不清楚这是否有用，但没什么坏处 */
		ConvertRowtypeExpr *fc_expr = (ConvertRowtypeExpr *) fc_node;

		fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, CollateExpr))
	{
		CollateExpr *fc_expr = (CollateExpr *) fc_node;

		fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, NullTest))
	{
		/* IS NOT NULL 可以被认为是严格的，但仅在顶层 */
		NullTest   *fc_expr = (NullTest *) fc_node;

		if (fc_top_level && fc_expr->nulltesttype == IS_NOT_NULL && !fc_expr->argisrow)
			fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, false);
	}
	else if (IsA(fc_node, BooleanTest))
	{
		/* 拒绝 NULL 的布尔测试在顶层是严格的 */
		BooleanTest *fc_expr = (BooleanTest *) fc_node;

		if (fc_top_level &&
			(fc_expr->booltesttype == IS_TRUE ||
			 fc_expr->booltesttype == IS_FALSE ||
			 fc_expr->booltesttype == IS_NOT_UNKNOWN))
			fc_result = fc_find_nonnullable_rels_walker((Node *) fc_expr->arg, false);
	}
	else if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		/*
		 * 如果包含的表达式强制任何关系为非空，
		 * 那么 PHV 也会如此。
		 */
		fc_result = fc_find_nonnullable_rels_walker((Node *) fc_phv->phexpr, fc_top_level);

		
/*
		 * 如果PHV的语法范围正好是一个关系，它将被强制
		 * 在该关系上进行评估，因此它的行为就像该关系的变量一样：
		 * 如果该关系的全部输出为null，则PHV也会如此。
		 * （如果语法范围是连接，我们知道如果整个连接为null，
		 * PHV也会为null；但那是AND语义，而我们需要的是
		 * find_nonnullable_rels结果的OR语义，因此我们无法利用
		 * 这一知识。）
		 */
		if (fc_phv->phlevelsup == 0 &&
			bms_membership(fc_phv->phrels) == BMS_SINGLETON)
			fc_result = bms_add_members(fc_result, fc_phv->phrels);
	}
	return fc_result;
}

/*
 * find_nonnullable_vars
 *		确定给定子句强制非空的变量。
 *
 * 返回在子句中以这样的方式被引用的所有零级变量的列表，
 * 使得如果任何这些变量为NULL，则子句不可能返回TRUE。
 * （在保守方面出现错误是可以的；因此这里的分析
 * 是简单的。）
 *
 * 这里的语义与contain_nonstrict_functions略有不同：
 * 那个函数关注于任意表达式的NULL结果，
 * 而这里我们假设输入是一个布尔表达式，并希望
 * 查看NULL输入是否会明显导致FALSE或NULL结果。
 * 我们期望表达式已经被AND/OR平坦化并转换为隐式AND格式。
 *
 * 结果是一个palloc的列表，但我们并没有复制成员变量节点。
 * 此外，我们也不费心去消除重复条目。
 *
 * 当扫描顶级AND/OR结构时，top_level为true；
 * 在这里，显示结果是FALSE或NULL就足够了。
 * 当我们下降到NOT或严格函数以下时，
 * top_level为false：现在我们必须能够证明子表达式为NULL。
 *
 * 我们不在这里使用expression_tree_walker，因为我们不想遍历
 * 很多类型的节点；只处理我们可以确定是严格的那些。
 */
List * find_nonnullable_vars(Node *fc_clause)
{
	return fc_find_nonnullable_vars_walker(fc_clause, true);
}

static List * fc_find_nonnullable_vars_walker(Node *fc_node, bool fc_top_level)
{
	List	   *fc_result = NIL;
	ListCell   *fc_l;

	if (fc_node == NULL)
		return NIL;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		if (fc_var->varlevelsup == 0)
			fc_result = list_make1(fc_var);
	}
	else if (IsA(fc_node, List))
	{
		/*
		 * 在顶层，我们正在检查隐式AND列表：如果任何一个
		 * 分支产生FALSE或NULL，则结果就是FALSE或NULL。
		 * 如果不在顶层，我们正在检查严格函数的参数：
		 * 如果其中任何一个产生NULL，则函数的结果必须为NULL。
		 * 因此在这两种情况下，非空变量的集合是分支中找到的
		 * 变量的并集，并且我们未修改地传递top_level标志。
		 */
		foreach(fc_l, (List *) fc_node)
		{
			fc_result = list_concat(fc_result,
								 fc_find_nonnullable_vars_walker(lfirst(fc_l),
															  fc_top_level));
		}
	}
	else if (IsA(fc_node, FuncExpr))
	{
		FuncExpr   *fc_expr = (FuncExpr *) fc_node;

		if (func_strict(fc_expr->funcid))
			fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->args, false);
	}
	else if (IsA(fc_node, OpExpr))
	{
		OpExpr	   *fc_expr = (OpExpr *) fc_node;

		set_opfuncid(fc_expr);
		if (func_strict(fc_expr->opfuncid))
			fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->args, false);
	}
	else if (IsA(fc_node, ScalarArrayOpExpr))
	{
		ScalarArrayOpExpr *fc_expr = (ScalarArrayOpExpr *) fc_node;

		if (fc_is_strict_saop(fc_expr, true))
			fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->args, false);
	}
	else if (IsA(fc_node, BoolExpr))
	{
		BoolExpr   *fc_expr = (BoolExpr *) fc_node;

		switch (fc_expr->boolop)
		{
			case AND_EXPR:
				/* 在顶层我们可以直接递归（到列表案例） */
				if (fc_top_level)
				{
					fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->args,
														  fc_top_level);
					break;
				}

				/*
				 * 在顶层以下，即使一个分支产生NULL，结果
				 * 也可能是FALSE（因此不是NULL）。然而，如果*所有*
				 * 分支产生NULL，则结果就是NULL，因此我们可以
				 * 取非空变量集合的交集，就像对于OR一样。
				 * 继续共享代码。
				 */
				/* FALL THRU */
			case OR_EXPR:

				/*
				 * 如果OR的所有分支都是严格的，我们就可以
				 * 对每个分支的非空变量集合取交集。
				 * 这对top_level的两种值都有效。
				 */
				foreach(fc_l, fc_expr->args)
				{
					List	   *fc_subresult;

					fc_subresult = fc_find_nonnullable_vars_walker(lfirst(fc_l),
															 fc_top_level);
					if (fc_result == NIL)	/* 第一个子结果？ */
						fc_result = fc_subresult;
					else
						fc_result = list_intersection(fc_result, fc_subresult);

					/*
					 * 如果交集为空，我们可以停止查找。
					 * 这也证明了上面有关首次子结果的测试的合理性。
					 */
					if (fc_result == NIL)
						break;
				}
				break;
			case NOT_EXPR:
				/* NOT 如果其参数为 NULL，则返回 NULL */
				fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->args,
													  false);
				break;
			default:
				elog(ERROR, "unrecognized boolop: %d", (int) fc_expr->boolop);
				break;
		}
	}
	else if (IsA(fc_node, RelabelType))
	{
		RelabelType *fc_expr = (RelabelType *) fc_node;

		fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, CoerceViaIO))
	{
		/* 不清楚这是否有用，但没什么坏处 */
		CoerceViaIO *fc_expr = (CoerceViaIO *) fc_node;

		fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, false);
	}
	else if (IsA(fc_node, ArrayCoerceExpr))
	{
		/* ArrayCoerceExpr 在数组级别是严格的；忽略 elemexpr */
		ArrayCoerceExpr *fc_expr = (ArrayCoerceExpr *) fc_node;

		fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, ConvertRowtypeExpr))
	{
		/* 不清楚这是否有用，但没什么坏处 */
		ConvertRowtypeExpr *fc_expr = (ConvertRowtypeExpr *) fc_node;

		fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, CollateExpr))
	{
		CollateExpr *fc_expr = (CollateExpr *) fc_node;

		fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, fc_top_level);
	}
	else if (IsA(fc_node, NullTest))
	{
		/* IS NOT NULL 可以被认为是严格的，但仅在顶层 */
		NullTest   *fc_expr = (NullTest *) fc_node;

		if (fc_top_level && fc_expr->nulltesttype == IS_NOT_NULL && !fc_expr->argisrow)
			fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, false);
	}
	else if (IsA(fc_node, BooleanTest))
	{
		/* 拒绝 NULL 的布尔测试在顶层是严格的 */
		BooleanTest *fc_expr = (BooleanTest *) fc_node;

		if (fc_top_level &&
			(fc_expr->booltesttype == IS_TRUE ||
			 fc_expr->booltesttype == IS_FALSE ||
			 fc_expr->booltesttype == IS_NOT_UNKNOWN))
			fc_result = fc_find_nonnullable_vars_walker((Node *) fc_expr->arg, false);
	}
	else if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		fc_result = fc_find_nonnullable_vars_walker((Node *) fc_phv->phexpr, fc_top_level);
	}
	return fc_result;
}

/*
 * find_forced_null_vars
 *		确定哪些变量必须为NULL，以便给定子句返回TRUE。
 *
 * 这是find_nonnullable_vars的补集：查找必须为NULL的零级变量，
 * 以使子句返回TRUE。（在保守方面出现错误是可以的；
 * 因此这里的分析是简单的。实际上，
 * 我们只在顶层检测简单的“变量IS NULL”测试。）
 *
 * 结果是一个palloc的列表，但我们并没有复制成员变量节点。
 * 此外，我们也不费心去消除重复条目。
 */
List * find_forced_null_vars(Node *fc_node)
{
	List	   *fc_result = NIL;
	Var		   *fc_var;
	ListCell   *fc_l;

	if (fc_node == NULL)
		return NIL;
	/* 使用子例程检查单子句情况 */
	fc_var = find_forced_null_var(fc_node);
	if (fc_var)
	{
		fc_result = list_make1(fc_var);
	}
	/* 否则，处理AND条件 */
	else if (IsA(fc_node, List))
	{
		/*
		 * 在顶层，我们正在检查一个隐式-AND 列表：如果任何分支产生 FALSE 或 NULL 那么结果就是 FALSE 或 NULL。
		 */
		foreach(fc_l, (List *) fc_node)
		{
			fc_result = list_concat(fc_result,
								 find_forced_null_vars(lfirst(fc_l)));
		}
	}
	else if (IsA(fc_node, BoolExpr))
	{
		BoolExpr   *fc_expr = (BoolExpr *) fc_node;

		/*
		 * 我们不考虑 OR 情况，因为没有人会写 "v1 IS NULL OR v1 IS NULL" 这种语句的可能性非常小。同样，NOT 情况也没有必要耗费代码去处理。
		 */
		if (fc_expr->boolop == AND_EXPR)
		{
			/* 在顶层我们可以直接递归（到列表案例） */
			fc_result = find_forced_null_vars((Node *) fc_expr->args);
		}
	}
	return fc_result;
}

/*
 * find_forced_null_var
 *		返回被给定子句强制为 NULL 的 Var，如果不是 IS NULL 类型的子句则返回 NULL。成功的条件是，该子句只能强制特定 Var 的 NULL 值，而不是其他任何条件。
 *
 * 这是 find_forced_null_vars() 的单子句情况，不考虑 AND 条件。它在 initsplan.c 中用于单个 qual 子句。之所以不直接应用 find_forced_null_vars() 是因为，如果 IS NULL 子句与其他内容的 AND 以某种方式在 AND/OR 扁平化中存活下来，initsplan.c 可能会被误导而丢弃整个子句，而实际上只有其 IS NULL 部分被证明是冗余的。
 */
Var * find_forced_null_var(Node *fc_node)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, NullTest))
	{
		/* 检查 var IS NULL */
		NullTest   *fc_expr = (NullTest *) fc_node;

		if (fc_expr->nulltesttype == IS_NULL && !fc_expr->argisrow)
		{
			Var		   *fc_var = (Var *) fc_expr->arg;

			if (fc_var && IsA(fc_var, Var) &&
				fc_var->varlevelsup == 0)
				return fc_var;
		}
	}
	else if (IsA(fc_node, BooleanTest))
	{
		/* var IS UNKNOWN 等同于 var IS NULL */
		BooleanTest *fc_expr = (BooleanTest *) fc_node;

		if (fc_expr->booltesttype == IS_UNKNOWN)
		{
			Var		   *fc_var = (Var *) fc_expr->arg;

			if (fc_var && IsA(fc_var, Var) &&
				fc_var->varlevelsup == 0)
				return fc_var;
		}
	}
	return NULL;
}

/*
 * 我们能将 ScalarArrayOpExpr 视为严格吗？
 *
 * 如果 "falseOK" 为真，那么可以将 "false" 结果视为严格，否则我们需要确保对 NULL 输入有实际的 NULL 结果。
 *
 * "foo op ALL array" 是严格的，前提是运算符是严格的 *并且* 我们能证明数组输入不是一个空数组。我们可以检查数组常量和 ARRAY[] 构造的情况。
 *
 * 如果运算符是严格的，那么 "foo op ANY array" 在 falseOK 的意义上是严格的。如果不满足 falseOK，则测试与 "foo op ALL array" 相同。
 */
static bool fc_is_strict_saop(ScalarArrayOpExpr *fc_expr, bool fc_falseOK)
{
	Node	   *fc_rightop;

	/* 包含的操作符必须是严格的。 */
	set_sa_opfuncid(fc_expr);
	if (!func_strict(fc_expr->opfuncid))
		return false;
	/* 如果是 ANY 且 falseOK，那么这就是我们需要检查的全部。 */
	if (fc_expr->useOr && fc_falseOK)
		return true;
	/* 否则，我们必须查看数组是否被证明为非空。 */
	Assert(list_length(fc_expr->args) == 2);
	fc_rightop = (Node *) lsecond(fc_expr->args);
	if (fc_rightop && IsA(fc_rightop, Const))
	{
		Datum		fc_arraydatum = ((Const *) fc_rightop)->constvalue;
		bool		fc_arrayisnull = ((Const *) fc_rightop)->constisnull;
		ArrayType  *fc_arrayval;
		int			fc_nitems;

		if (fc_arrayisnull)
			return false;
		fc_arrayval = DatumGetArrayTypeP(fc_arraydatum);
		fc_nitems = ArrayGetNItems(ARR_NDIM(fc_arrayval), ARR_DIMS(fc_arrayval));
		if (fc_nitems > 0)
			return true;
	}
	else if (fc_rightop && IsA(fc_rightop, ArrayExpr))
	{
		ArrayExpr  *fc_arrayexpr = (ArrayExpr *) fc_rightop;

		if (fc_arrayexpr->elements != NIL && !fc_arrayexpr->multidims)
			return true;
	}
	return false;
}


/*****************************************************************************
 *		检查“伪常量”子句
 *****************************************************************************/

/*
 * is_pseudo_constant_clause
 *	  检测一个表达式是否为“伪常量”，即它不包含当前查询级别的变量，也不使用易变函数。
 *	  这样的表达式不一定是真正的常量：它仍然可以包含 Params 和外层的 Vars，更不用说其结果可能在不同语句中变化的函数。然而，表达式在当前查询的任何一次扫描中的值都是常量，因此可以用作，例如索引扫描键。（实际上，对于索引扫描键的条件比这要弱；参见 is_pseudo_constant_for_index()。）
 *
 * 注意：此函数未测试一类非常重要的非常量表达式，即聚合（Aggrefs）。在当前的用法中，这仅应用于 WHERE 子句，因此检查 Aggrefs 将是一种浪费循环；但是如果您希望了解其他上下文中的伪常量性，请确保检查 contain_agg_clause()。窗口函数（WindowFuncs）也一样。
 */
bool is_pseudo_constant_clause(Node *fc_clause)
{
	/*
	 * 我们可以在一次递归扫描中实现此检查。但由于易变函数的检查既适度昂贵又不太可能失败，因此首先查找 Vars，然后仅在未找到 Vars 时检查易变函数似乎更好。
	 */
	if (!contain_var_clause(fc_clause) &&
		!contain_volatile_functions(fc_clause))
		return true;
	return false;
}

/*
 * is_pseudo_constant_clause_relids
 *	  与上述相同，区别在于调用者已经获得表达式的变量成员资格；这让我们可以避开 contain_var_clause() 的扫描。
 */
bool is_pseudo_constant_clause_relids(Node *fc_clause, Relids fc_relids)
{
	if (bms_is_empty(fc_relids) &&
		!contain_volatile_functions(fc_clause))
		return true;
	return false;
}


/*****************************************************************************
 *																			 *
 *		通用子句操作常规												      *
 *																			 *
 *****************************************************************************/

/*
 * NumRelids
 *		（以前称为 clause_relids）
 *
 * 返回 'clause' 中引用的不同关系的数量。
 */
int NumRelids(PlannerInfo *fc_root, Node *fc_clause)
{
	Relids		fc_varnos = pull_varnos(fc_root, fc_clause);
	int			fc_result = bms_num_members(fc_varnos);

	bms_free(fc_varnos);
	return fc_result;
}

/*
 * CommuteOpExpr：交换二元操作符子句
 *
 * XXX 子句被破坏性修改！
 */
void CommuteOpExpr(OpExpr *fc_clause)
{
	Oid			fc_opoid;
	Node	   *fc_temp;

	/* 确保检查：调用者如果失败则需要负责 */
	if (!is_opclause(fc_clause) ||
		list_length(fc_clause->args) != 2)
		elog(ERROR, "cannot commute non-binary-operator clause");

	fc_opoid = get_commutator(fc_clause->opno);

	if (!OidIsValid(fc_opoid))
		elog(ERROR, "could not find commutator for operator %u",
			 fc_clause->opno);

	/*
	 * 在原地修改子句！
	 */
	fc_clause->opno = fc_opoid;
	fc_clause->opfuncid = InvalidOid;
	/* opresulttype，opretset，opcollid，inputcollid 不必更改 */

	fc_temp = linitial(fc_clause->args);
	linitial(fc_clause->args) = lsecond(fc_clause->args);
	lsecond(fc_clause->args) = fc_temp;
}

/*
 * eval_const_expressions 的辅助功能：检查属性的数据类型是否仍然是解析表达式时的数据类型。这是为了防止在 ALTER COLUMN TYPE 后的不当简化。（XXX 我们可能还需要在其他地方进行类似的检查？）
 *
 * rowtypeid 可能来自整个行 Var，因此它可以是复合类型的域，但出于此目的我们只关心检查包含字段的类型。
 */
static bool fc_rowtype_field_matches(Oid fc_rowtypeid, int fc_fieldnum,
					  Oid fc_expectedtype, int32 fc_expectedtypmod,
					  Oid fc_expectedcollation)
{
	TupleDesc	fc_tupdesc;
	Form_pg_attribute fc_attr;

	/* RECORD 没有问题，因为没有办法 ALTER 这样的类型 */
	if (fc_rowtypeid == RECORDOID)
		return true;
	fc_tupdesc = lookup_rowtype_tupdesc_domain(fc_rowtypeid, -1, false);
	if (fc_fieldnum <= 0 || fc_fieldnum > fc_tupdesc->natts)
	{
		ReleaseTupleDesc(fc_tupdesc);
		return false;
	}
	fc_attr = TupleDescAttr(fc_tupdesc, fc_fieldnum - 1);
	if (fc_attr->attisdropped ||
		fc_attr->atttypid != fc_expectedtype ||
		fc_attr->atttypmod != fc_expectedtypmod ||
		fc_attr->attcollation != fc_expectedcollation)
	{
		ReleaseTupleDesc(fc_tupdesc);
		return false;
	}
	ReleaseTupleDesc(fc_tupdesc);
	return true;
}


/*--------------------
 * eval_const_expressions
 *
 * 减少给定表达式树中任何可识别的常量子表达式，例如 "2 + 2" => "4"。
 * 更有趣的是，即使包含非恒定子表达式，我们也可以减少某些布尔表达式：
 * "x OR true" => "true"，无论子表达式 x 是什么。
 * （XXX 我们假设没有这样的子表达式会有重要的副作用，这在用户定义函数存在的情况下并不一定是一个好的假设；
 * 我们需要一个 pg_proc 标志来防止丢弃函数的执行吗？）
 *
 * 我们理解某些函数即使在常量输入的情况下也可能返回非恒定结果，例如 "nextval()" 是经典的例子。
 * 在 pg_proc 中没有标记为 "immutable" 的函数不会在这里被预计算，尽管我们会尽可能减少它们的参数。
 *
 * 每当通过常量表达式求值或内联消除表达式中的函数时，我们都会将该函数添加到
 * root->glob->invalItems。这确保计划被视为依赖于这样的函数，即使它们不再被引用。
 *
 * 我们假设树已经过类型检查，并且只包含合理的运算符和函数。
 *
 * 注意：“root”可以传递为 NULL，如果调用者不希望进行任何
 * 参数替换或接收内联函数的信息。
 *
 * 注意：规划器假设这将始终将嵌套的 AND 和 OR 条件展平为 N-参数形式。
 * 参见 prepqual.c 中的注释。
 *
 * 注意：另一个关键效果是，任何需要默认参数的函数调用将被展开，
 * 命名参数调用将被转换为位置表示法。执行器将不会处理这两者。
 *--------------------
 */
Node * eval_const_expressions(PlannerInfo *fc_root, Node *fc_node)
{
	eval_const_expressions_context fc_context;

	if (fc_root)
		fc_context.boundParams = fc_root->glob->boundParams;	/* 绑定参数 */
	else
		fc_context.boundParams = NULL;
	fc_context.root = fc_root;		/* 用于内联函数的依赖关系 */
	fc_context.active_fns = NIL;	/* 没有递归简化任何内容 */
	fc_context.case_val = NULL;	/* 未检查任何 CASE */
	fc_context.estimate = false;	/* 仅安全转换 */
	return fc_eval_const_expressions_mutator(fc_node, &fc_context);
}

#define MIN_ARRAY_SIZE_FOR_HASHED_SAOP 9
/*--------------------
 * convert_saop_to_hashed_saop
 *
 * 递归搜索 'node' 中的 ScalarArrayOpExpr，并填写任何看起来
 * 适合使用哈希表而不是线性搜索进行评估的 ScalarArrayOpExpr 的哈希函数。
 *
 * 只有在满足以下所有条件时，我们才会使用哈希表：
 * 1. 数组的第二个参数仅包含 Consts。
 * 2. useOr 为 true，或者 ScalarArrayOpExpr 的 opno 存在有效的取反运算符。
 * 3. 左操作数和右操作数都有有效的哈希函数，并且这些哈希函数相同。
 * 4. 如果数组包含足够的元素，以至于我们认为使用哈希表比线性搜索更有价值。
 */
void convert_saop_to_hashed_saop(Node *fc_node)
{
	(void) fc_convert_saop_to_hashed_saop_walker(fc_node, NULL);
}

static bool fc_convert_saop_to_hashed_saop_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;

	if (IsA(fc_node, ScalarArrayOpExpr))
	{
		ScalarArrayOpExpr *fc_saop = (ScalarArrayOpExpr *) fc_node;
		Expr	   *fc_arrayarg = (Expr *) lsecond(fc_saop->args);
		Oid			fc_lefthashfunc;
		Oid			fc_righthashfunc;

		if (fc_arrayarg && IsA(fc_arrayarg, Const) &&
			!((Const *) fc_arrayarg)->constisnull)
		{
			if (fc_saop->useOr)
			{
				if (get_op_hash_functions(fc_saop->opno, &fc_lefthashfunc, &fc_righthashfunc) &&
					fc_lefthashfunc == fc_righthashfunc)
				{
					Datum		fc_arrdatum = ((Const *) fc_arrayarg)->constvalue;
					ArrayType  *fc_arr = (ArrayType *) DatumGetPointer(fc_arrdatum);
					int			fc_nitems;

					/*
					 * 仅当数组看起来足够大，以至于值得哈希而不是进行
					 * 线性搜索时才填写哈希函数。
					 */
					fc_nitems = ArrayGetNItems(ARR_NDIM(fc_arr), ARR_DIMS(fc_arr));

					if (fc_nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
					{
						/* 看起来不错。填写哈希函数 */
						fc_saop->hashfuncid = fc_lefthashfunc;
					}
					return true;
				}
			}
			else				/* !saop->useOr */
			{
				Oid			fc_negator = get_negator(fc_saop->opno);

				/*
				 * 检查这是否是一个使用可哈希的运算符的 NOT IN。如果是的话，我们仍然可以构建一个哈希表，并确保查找的项不在哈希表中。
				 */
				if (OidIsValid(fc_negator) &&
					get_op_hash_functions(fc_negator, &fc_lefthashfunc, &fc_righthashfunc) &&
					fc_lefthashfunc == fc_righthashfunc)
				{
					Datum		fc_arrdatum = ((Const *) fc_arrayarg)->constvalue;
					ArrayType  *fc_arr = (ArrayType *) DatumGetPointer(fc_arrdatum);
					int			fc_nitems;

					/*
					 * 仅当数组看起来足够大，以至于值得哈希而不是进行
					 * 线性搜索时才填写哈希函数。
					 */
					fc_nitems = ArrayGetNItems(ARR_NDIM(fc_arr), ARR_DIMS(fc_arr));

					if (fc_nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
					{
						/* 看起来不错。填写哈希函数 */
						fc_saop->hashfuncid = fc_lefthashfunc;

						/*
						 * 同样设置 negfuncid。执行器将需要这个来执行哈希表查找。
						 */
						fc_saop->negfuncid = get_opcode(fc_negator);
					}
					return true;
				}
			}
		}
	}

	return expression_tree_walker(fc_node, fc_convert_saop_to_hashed_saop_walker, NULL);
}


/*--------------------
 * estimate_expression_value
 *
 * 此函数尝试估计一个表达式的值以用于规划。 本质上，它是 eval_const_expressions() 的一个更激进的版本：我们将执行不一定 100% 安全的常量简化，但对于估计目的来说是合理的。
 *
 * 当前在这种模式下采取的额外步骤是：
 * 1. 用 Params 的值进行替换，当调用 planner() 的调用者提供了一个绑定的 Param 值时，即使该 Param 没有标记为常量。这实际上意味着我们使用的 Param 的第一个提供值进行规划。
 * 2. 将稳定的以及不可变的函数折叠为常量。
 * 3. 将 PlaceHolderVar 节点简化为其包含的表达式。
 *--------------------
 */
Node * estimate_expression_value(PlannerInfo *fc_root, Node *fc_node)
{
	eval_const_expressions_context fc_context;

	fc_context.boundParams = fc_root->glob->boundParams;	/* 绑定参数 */
	/* 我们不需要将计划标记为依赖于内联函数 */
	fc_context.root = NULL;
	fc_context.active_fns = NIL;	/* 没有递归简化任何内容 */
	fc_context.case_val = NULL;	/* 未检查任何 CASE */
	fc_context.estimate = true;	/* 安全的转换OK */
	return fc_eval_const_expressions_mutator(fc_node, &fc_context);
}

/*
 * eval_const_expressions_mutator 中的通用情况是使用 expression_tree_mutator 递归，该函数将不改变给定节点而复制，但尽可能简化其参数（如果有）。如果节点本身进行不可变处理，并且它的每个参数都被简化为 Const，那么我们可以使用 evaluate_expr 将其简化为 Const。（某些节点类型需要更复杂的逻辑；例如，一个 CASE 表达式即使不是它的所有子树也可以被简化为常量。）
 */
#define ece_generic_processing(node) \
	expression_tree_mutator((Node *) (node), fc_eval_const_expressions_mutator, \
							(void *) fc_context)

/*
 * 检查给定节点的所有参数是否被简化为 Const。
 * 通过直接进入 expression_tree_walker，contain_non_const_walker 不用于节点本身，仅用于其子节点。
 */
#define ece_all_arguments_const(node) \
	(!expression_tree_walker((Node *) (node), fc_contain_non_const_walker, NULL))

/* 应用 evaluate_expr 的通用宏 */
#define ece_evaluate_expr(node) \
	((Node *) evaluate_expr((Expr *) (node), \
							exprType((Node *) (node)), \
							exprTypmod((Node *) (node)), \
							exprCollation((Node *) (node))))

/*
 * eval_const_expressions/estimate_expression_value 的递归核心
 */
static Node * fc_eval_const_expressions_mutator(Node *fc_node,
							   eval_const_expressions_context *fc_context)
{

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	if (fc_node == NULL)
		return NULL;
	switch (nodeTag(fc_node))
	{
		case T_Param:
			{
				Param	   *fc_param = (Param *) fc_node;
				ParamListInfo fc_paramLI = fc_context->boundParams;

				/* 查看我们是否获得了这个 Param 的值 */
				if (fc_param->paramkind == PARAM_EXTERN &&
					fc_paramLI != NULL &&
					fc_param->paramid > 0 &&
					fc_param->paramid <= fc_paramLI->numParams)
				{
					ParamExternData *fc_prm;
					ParamExternData fc_prmdata;

					/*
					 * 给 hook 一次机会，如果参数是动态的。告诉它这个获取是推测性的，因此如果参数不可用，它应避免报错。
					 */
					if (fc_paramLI->paramFetch != NULL)
						fc_prm = fc_paramLI->paramFetch(fc_paramLI, fc_param->paramid,
												  true, &fc_prmdata);
					else
						fc_prm = &fc_paramLI->params[fc_param->paramid - 1];

					/*
					 * 我们不仅仅检查 OidIsValid，而是坚持确保获取的类型与 Param 匹配，以防 hook 做了意外的事情。不过在这里没有必要抛出错误；留给运行时处理。
					 */
					if (OidIsValid(fc_prm->ptype) &&
						fc_prm->ptype == fc_param->paramtype)
					{
						/* 可以替换参数值吗？ */
						if (fc_context->estimate ||
							(fc_prm->pflags & PARAM_FLAG_CONST))
						{
							/*
							 * 返回一个代表参数值的 Const。
							 * 必须复制按引用传递的数据类型，因为 Param 可能在一个比我们的输出计划生命更短的内存上下文中。
							 */
							int16		fc_typLen;
							bool		fc_typByVal;
							Datum		fc_pval;
							Const	   *fc_con;

							get_typlenbyval(fc_param->paramtype,
											&fc_typLen, &fc_typByVal);
							if (fc_prm->isnull || fc_typByVal)
								fc_pval = fc_prm->value;
							else
								fc_pval = datumCopy(fc_prm->value, fc_typByVal, fc_typLen);
							fc_con = makeConst(fc_param->paramtype,
											fc_param->paramtypmod,
											fc_param->paramcollid,
											(int) fc_typLen,
											fc_pval,
											fc_prm->isnull,
											fc_typByVal);
							fc_con->location = fc_param->location;
							return (Node *) fc_con;
						}
					}
				}

				/*
				 * 不可替换，因此只需复制 Param（无需递归）
				 */
				return (Node *) copyObject(fc_param);
			}
		case T_WindowFunc:
			{
				WindowFunc *fc_expr = (WindowFunc *) fc_node;
				Oid			fc_funcid = fc_expr->winfnoid;
				List	   *fc_args;
				Expr	   *fc_aggfilter;
				HeapTuple	fc_func_tuple;
				WindowFunc *fc_newexpr;

				/*
				 * 我们不能真正简化 WindowFunc 节点，但我们绝不能仅仅通过默认处理，因为我们必须对其参数列表应用 expand_function_arguments。这会处理插入默认参数和扩展命名参数表示法。
				 */
				fc_func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
				if (!HeapTupleIsValid(fc_func_tuple))
					elog(ERROR, "cache lookup failed for function %u", fc_funcid);

				fc_args = expand_function_arguments(fc_expr->args,
												 false, fc_expr->wintype,
												 fc_func_tuple);

				ReleaseSysCache(fc_func_tuple);

				/* 现在，递归简化参数（这是一个 List） */
				fc_args = (List *)
					expression_tree_mutator((Node *) fc_args,
											fc_eval_const_expressions_mutator,
											(void *) fc_context);
				/* ... 和过滤表达式，它不是 */
				fc_aggfilter = (Expr *)
					fc_eval_const_expressions_mutator((Node *) fc_expr->aggfilter,
												   fc_context);

				/* 并构建替换的 WindowFunc 节点 */
				fc_newexpr = makeNode(WindowFunc);
				fc_newexpr->winfnoid = fc_expr->winfnoid;
				fc_newexpr->wintype = fc_expr->wintype;
				fc_newexpr->wincollid = fc_expr->wincollid;
				fc_newexpr->inputcollid = fc_expr->inputcollid;
				fc_newexpr->args = fc_args;
				fc_newexpr->aggfilter = fc_aggfilter;
				fc_newexpr->winref = fc_expr->winref;
				fc_newexpr->winstar = fc_expr->winstar;
				fc_newexpr->winagg = fc_expr->winagg;
				fc_newexpr->location = fc_expr->location;

				return (Node *) fc_newexpr;
			}
		case T_FuncExpr:
			{
				FuncExpr   *fc_expr = (FuncExpr *) fc_node;
				List	   *fc_args = fc_expr->args;
				Expr	   *fc_simple;
				FuncExpr   *fc_newexpr;

				/*
				 * 运算符/函数简化的代码相当冗长，因此将其拆分为一个单独的函数。 注意：exprTypmod 通常为 FuncExpr 返回 -1，但在节点可以识别为长度强制转换时不是；如果是这样，我们希望在最终的 Const 中保留 typmod。
				 */
				fc_simple = fc_simplify_function(fc_expr->funcid,
										   fc_expr->funcresulttype,
										   exprTypmod(fc_node),
										   fc_expr->funccollid,
										   fc_expr->inputcollid,
										   &fc_args,
										   fc_expr->funcvariadic,
										   true,
										   true,
										   fc_context);
				if (fc_simple)		/* 成功简化它 */
					return (Node *) fc_simple;

				/*
				 * 表达式不能进一步简化，因此使用可能已简化的参数构建并返回一个替换的 FuncExpr 节点。 请注意，我们还将参数列表转换为位置表示法。
				 */
				fc_newexpr = makeNode(FuncExpr);
				fc_newexpr->funcid = fc_expr->funcid;
				fc_newexpr->funcresulttype = fc_expr->funcresulttype;
				fc_newexpr->funcretset = fc_expr->funcretset;
				fc_newexpr->funcvariadic = fc_expr->funcvariadic;
				fc_newexpr->funcformat = fc_expr->funcformat;
				fc_newexpr->funccollid = fc_expr->funccollid;
				fc_newexpr->inputcollid = fc_expr->inputcollid;
				fc_newexpr->args = fc_args;
				fc_newexpr->location = fc_expr->location;
				return (Node *) fc_newexpr;
			}
		case T_OpExpr:
			{
				OpExpr	   *fc_expr = (OpExpr *) fc_node;
				List	   *fc_args = fc_expr->args;
				Expr	   *fc_simple;
				OpExpr	   *fc_newexpr;

				/*
				 * 需要获取底层函数的 OID。可以在输入上进行如此程度的修改。
				 */
				set_opfuncid(fc_expr);

				/*
				 * 运算符/函数简化的代码相当冗长，因此将其拆分为一个单独的函数。
				 */
				fc_simple = fc_simplify_function(fc_expr->opfuncid,
										   fc_expr->opresulttype, -1,
										   fc_expr->opcollid,
										   fc_expr->inputcollid,
										   &fc_args,
										   false,
										   true,
										   true,
										   fc_context);
				if (fc_simple)		/* 成功简化它 */
					return (Node *) fc_simple;

				/*
				 * 如果运算符是布尔相等或不相等，我们知道如何简化涉及一个常量和一个非常量参数的情况。
				 */
				if (fc_expr->opno == BooleanEqualOperator ||
					fc_expr->opno == BooleanNotEqualOperator)
				{
					fc_simple = (Expr *) fc_simplify_boolean_equality(fc_expr->opno,
																fc_args);
					if (fc_simple) /* 成功简化它 */
						return (Node *) fc_simple;
				}

				/*
				 * 表达式不能进一步简化，因此使用可能已简化的参数构建并返回一个替换的 OpExpr 节点。
				 */
				fc_newexpr = makeNode(OpExpr);
				fc_newexpr->opno = fc_expr->opno;
				fc_newexpr->opfuncid = fc_expr->opfuncid;
				fc_newexpr->opresulttype = fc_expr->opresulttype;
				fc_newexpr->opretset = fc_expr->opretset;
				fc_newexpr->opcollid = fc_expr->opcollid;
				fc_newexpr->inputcollid = fc_expr->inputcollid;
				fc_newexpr->args = fc_args;
				fc_newexpr->location = fc_expr->location;
				return (Node *) fc_newexpr;
			}
		case T_DistinctExpr:
			{
				DistinctExpr *fc_expr = (DistinctExpr *) fc_node;
				List	   *fc_args;
				ListCell   *fc_arg;
				bool		fc_has_null_input = false;
				bool		fc_all_null_input = true;
				bool		fc_has_nonconst_input = false;
				Expr	   *fc_simple;
				DistinctExpr *fc_newexpr;

				
/*
				 * 在 DistinctExpr 的参数中减少常量。我们知道
				 * args 要么是 NIL 要么是 List 节点，所以我们可以直接调用
				 * expression_tree_mutator，而不是递归调用自身。
				 */
				fc_args = (List *) expression_tree_mutator((Node *) fc_expr->args,
														fc_eval_const_expressions_mutator,
														(void *) fc_context);

				/*
				 * 我们必须自己检查 NULL，因为 DistinctExpr 对 NULL 输入的结果
				 * 与底层操作符的结果不同。
				 */
				foreach(fc_arg, fc_args)
				{
					if (IsA(lfirst(fc_arg), Const))
					{
						fc_has_null_input |= ((Const *) lfirst(fc_arg))->constisnull;
						fc_all_null_input &= ((Const *) lfirst(fc_arg))->constisnull;
					}
					else
						fc_has_nonconst_input = true;
				}

				/* 所有常量？那么可以优化掉这个 */
				if (!fc_has_nonconst_input)
				{
					/* 所有为 null？那么不distinct */
					if (fc_all_null_input)
						return makeBoolConst(false, false);

					/* 一个为 null？那么distinct */
					if (fc_has_null_input)
						return makeBoolConst(true, false);

					/* 否则尝试评估 '=' 操作符 */
					/* （不过不可以尝试内联它！） */

					/*
					 * 需要获取底层函数的 OID。可以在输入上
					 * 做到这一点。
					 */
					set_opfuncid((OpExpr *) fc_expr);	/* 依赖于结构
													 * 等价性 */

					/*
					 * op/func 简化的代码比较庞大，所以将其
					 * 拆分为单独的函数。
					 */
					fc_simple = fc_simplify_function(fc_expr->opfuncid,
											   fc_expr->opresulttype, -1,
											   fc_expr->opcollid,
											   fc_expr->inputcollid,
											   &fc_args,
											   false,
											   false,
											   false,
											   fc_context);
					if (fc_simple) /* 成功简化它 */
					{
						/*
						 * 由于底层操作符是 "=", 必须取反
						 * 其结果
						 */
						Const	   *fc_csimple = castNode(Const, fc_simple);

						fc_csimple->constvalue =
							BoolGetDatum(!DatumGetBool(fc_csimple->constvalue));
						return (Node *) fc_csimple;
					}
				}

				/*
				 * 表达式无法进一步简化，因此构建
				 * 并返回一个使用可能简化参数的替换 DistinctExpr 节点。
				 */
				fc_newexpr = makeNode(DistinctExpr);
				fc_newexpr->opno = fc_expr->opno;
				fc_newexpr->opfuncid = fc_expr->opfuncid;
				fc_newexpr->opresulttype = fc_expr->opresulttype;
				fc_newexpr->opretset = fc_expr->opretset;
				fc_newexpr->opcollid = fc_expr->opcollid;
				fc_newexpr->inputcollid = fc_expr->inputcollid;
				fc_newexpr->args = fc_args;
				fc_newexpr->location = fc_expr->location;
				return (Node *) fc_newexpr;
			}
		case T_NullIfExpr:
			{
				NullIfExpr *fc_expr;
				ListCell   *fc_arg;
				bool		fc_has_nonconst_input = false;

				/* 复制节点并常量简化其参数 */
				fc_expr = (NullIfExpr *) ece_generic_processing(fc_node);

				/* 如果任一参数为 NULL，则它们不能相等 */
				foreach(fc_arg, fc_expr->args)
				{
					if (!IsA(lfirst(fc_arg), Const))
						fc_has_nonconst_input = true;
					else if (((Const *) lfirst(fc_arg))->constisnull)
						return (Node *) linitial(fc_expr->args);
				}

				/*
				 * 需要在检查函数是否可以评估之前获取
				 * 底层函数的 OID。
				 */
				set_opfuncid((OpExpr *) fc_expr);

				if (!fc_has_nonconst_input &&
					fc_ece_function_is_safe(fc_expr->opfuncid, fc_context))
					return ece_evaluate_expr(fc_expr);

				return (Node *) fc_expr;
			}
		case T_ScalarArrayOpExpr:
			{
				ScalarArrayOpExpr *fc_saop;

				/* 复制节点并常量简化其参数 */
				fc_saop = (ScalarArrayOpExpr *) ece_generic_processing(fc_node);

				/* 确保我们知道底层函数 */
				set_sa_opfuncid(fc_saop);

				/*
				 * 如果所有参数都是 Const，且它是安全函数，我们
				 * 可以折叠为一个常量
				 */
				if (ece_all_arguments_const(fc_saop) &&
					fc_ece_function_is_safe(fc_saop->opfuncid, fc_context))
					return ece_evaluate_expr(fc_saop);
				return (Node *) fc_saop;
			}
		case T_BoolExpr:
			{
				BoolExpr   *fc_expr = (BoolExpr *) fc_node;

				switch (fc_expr->boolop)
				{
					case OR_EXPR:
						{
							List	   *fc_newargs;
							bool		fc_haveNull = false;
							bool		fc_forceTrue = false;

							fc_newargs = fc_simplify_or_arguments(fc_expr->args,
															fc_context,
															&fc_haveNull,
															&fc_forceTrue);
							if (fc_forceTrue)
								return makeBoolConst(true, false);
							if (fc_haveNull)
								fc_newargs = lappend(fc_newargs,
												  makeBoolConst(false, true));
							/* 如果所有输入都为 FALSE，结果为 FALSE */
							if (fc_newargs == NIL)
								return makeBoolConst(false, false);

							/*
							 * 如果只有一个非Const或NULL输入，则就是
							 * 结果
							 */
							if (list_length(fc_newargs) == 1)
								return (Node *) linitial(fc_newargs);
							/* 否则我们仍需要一个 OR 节点 */
							return (Node *) make_orclause(fc_newargs);
						}
					case AND_EXPR:
						{
							List	   *fc_newargs;
							bool		fc_haveNull = false;
							bool		fc_forceFalse = false;

							fc_newargs = fc_simplify_and_arguments(fc_expr->args,
															 fc_context,
															 &fc_haveNull,
															 &fc_forceFalse);
							if (fc_forceFalse)
								return makeBoolConst(false, false);
							if (fc_haveNull)
								fc_newargs = lappend(fc_newargs,
												  makeBoolConst(false, true));
							/* 如果所有输入都为 TRUE，结果为 TRUE */
							if (fc_newargs == NIL)
								return makeBoolConst(true, false);

							/*
							 * 如果只有一个非Const或NULL输入，则就是
							 * 结果
							 */
							if (list_length(fc_newargs) == 1)
								return (Node *) linitial(fc_newargs);
							/* 否则我们仍然需要一个 AND 节点 */
							return (Node *) make_andclause(fc_newargs);
						}
					case NOT_EXPR:
						{
							Node	   *fc_arg;

							Assert(list_length(fc_expr->args) == 1);
							fc_arg = fc_eval_const_expressions_mutator(linitial(fc_expr->args),
																 fc_context);

							/*
							 * 使用 negate_clause() 查看是否可以简化
							 * 不操作。
							 */
							return negate_clause(fc_arg);
						}
					default:
						elog(ERROR, "unrecognized boolop: %d",
							 (int) fc_expr->boolop);
						break;
				}
				break;
			}
		case T_SubPlan:
		case T_AlternativeSubPlan:

			/*
			 * 返回一个未变的 SubPlan --- 做任何事都太晚了。
			 *
			 * XXX 我们应该在这里使用 ereport() 吗？可能这个例程
			 * 不应该在 SubPlan 创建后被调用。
			 */
			return fc_node;
		case T_RelabelType:
			{
				RelabelType *fc_relabel = (RelabelType *) fc_node;
				Node	   *fc_arg;

				/* 简化输入 ... */
				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_relabel->arg,
													 fc_context);
				/* ... 并在需要时附加一个新的 RelabelType 节点 */
				return applyRelabelType(fc_arg,
										fc_relabel->resulttype,
										fc_relabel->resulttypmod,
										fc_relabel->resultcollid,
										fc_relabel->relabelformat,
										fc_relabel->location,
										true);
			}
		case T_CoerceViaIO:
			{
				CoerceViaIO *fc_expr = (CoerceViaIO *) fc_node;
				List	   *fc_args;
				Oid			fc_outfunc;
				bool		fc_outtypisvarlena;
				Oid			fc_infunc;
				Oid			fc_intypioparam;
				Expr	   *fc_simple;
				CoerceViaIO *fc_newexpr;

				/* 创建一个 List，以便我们可以使用 simplify_function */
				fc_args = list_make1(fc_expr->arg);

				/*
				 * CoerceViaIO 表示调用源类型的输出
				 * 函数，然后调用结果类型的输入函数。因此，尝试
				 * 将其简化为两次此类函数调用的堆栈。首先我们需要知道这些函数是什么。
				 *
				 * 注意，假设强制转换函数不关心输入排序，所以我们
				 * 只是传递 InvalidOid 作为输入。
				 */
				getTypeOutputInfo(exprType((Node *) fc_expr->arg),
								  &fc_outfunc, &fc_outtypisvarlena);
				getTypeInputInfo(fc_expr->resulttype,
								 &fc_infunc, &fc_intypioparam);

				fc_simple = fc_simplify_function(fc_outfunc,
										   CSTRINGOID, -1,
										   InvalidOid,
										   InvalidOid,
										   &fc_args,
										   false,
										   true,
										   true,
										   fc_context);
				if (fc_simple)		/* 成功简化输出函数 */
				{
					/*
					 * 输入函数可能需要 1 到 3 个参数。我们总是
					 * 提供所有三个，信任后续不会有任何抱怨。
					 */
					fc_args = list_make3(fc_simple,
									  makeConst(OIDOID,
												-1,
												InvalidOid,
												sizeof(Oid),
												ObjectIdGetDatum(fc_intypioparam),
												false,
												true),
									  makeConst(INT4OID,
												-1,
												InvalidOid,
												sizeof(int32),
												Int32GetDatum(-1),
												false,
												true));

					fc_simple = fc_simplify_function(fc_infunc,
											   fc_expr->resulttype, -1,
											   fc_expr->resultcollid,
											   InvalidOid,
											   &fc_args,
											   false,
											   false,
											   true,
											   fc_context);
					if (fc_simple) /* 成功简化输入函数 */
						return (Node *) fc_simple;
				}

				/*
				 * 表达式无法进一步简化，因此构建
				 * 并返回一个使用可能简化参数的替换 CoerceViaIO 节点。
				 */
				fc_newexpr = makeNode(CoerceViaIO);
				fc_newexpr->arg = (Expr *) linitial(fc_args);
				fc_newexpr->resulttype = fc_expr->resulttype;
				fc_newexpr->resultcollid = fc_expr->resultcollid;
				fc_newexpr->coerceformat = fc_expr->coerceformat;
				fc_newexpr->location = fc_expr->location;
				return (Node *) fc_newexpr;
			}
		case T_ArrayCoerceExpr:
			{
				ArrayCoerceExpr *fc_ac = makeNode(ArrayCoerceExpr);
				Node	   *fc_save_case_val;

				/*
				 * 复制节点并常量简化其参数。我们不能
				 * 在这里使用 ece_generic_processing()，因为我们需要在处理 elemexpr 时
				 * 修改 case_val。
				 */
				memcpy(fc_ac, fc_node, sizeof(ArrayCoerceExpr));
				fc_ac->arg = (Expr *)
					fc_eval_const_expressions_mutator((Node *) fc_ac->arg,
												   fc_context);

				/*
				 * 为 elemexpr 中包含的 CaseTestExpr 节点设置准备工作。
				 * 我们必须防止它吸收任何外部 CASE 值。
				 */
				fc_save_case_val = fc_context->case_val;
				fc_context->case_val = NULL;

				fc_ac->elemexpr = (Expr *)
					fc_eval_const_expressions_mutator((Node *) fc_ac->elemexpr,
												   fc_context);

				fc_context->case_val = fc_save_case_val;

				/*
				 * 如果常量参数和每个元素的表达式是
				 * 不可变的，我们可以将整个内容简化为一个常量。
				 * 例外：虽然 contain_mutable_functions 认为
				 * CoerceToDomain 对历史原因来说是不可变的，但我们在这里
				 * 不这么做；这确保了对数组-域的强制转换
				 * 在运行时之前不会应用域的约束。
				 */
				if (fc_ac->arg && IsA(fc_ac->arg, Const) &&
					fc_ac->elemexpr && !IsA(fc_ac->elemexpr, CoerceToDomain) &&
					!contain_mutable_functions((Node *) fc_ac->elemexpr))
					return ece_evaluate_expr(fc_ac);

				return (Node *) fc_ac;
			}
		case T_CollateExpr:
			{
				/*
				 * 我们用 RelabelType 替换 CollateExpr，以提高
				 * 表达式表示的统一性，从而简化
				 * 表达式的比较。因此，这看起来非常接近
				 * RelabelType 的情况，我们可以应用相同的
				 * 优化，以避免不必要的 RelabelTypes。
				 */
				CollateExpr *fc_collate = (CollateExpr *) fc_node;
				Node	   *fc_arg;

				/* 简化输入 ... */
				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_collate->arg,
													 fc_context);
				/* ... 并在需要时附加一个新的 RelabelType 节点 */
				return applyRelabelType(fc_arg,
										exprType(fc_arg),
										exprTypmod(fc_arg),
										fc_collate->collOid,
										COERCE_IMPLICIT_CAST,
										fc_collate->location,
										true);
			}
		case T_CaseExpr:
			{
				/*----------
				 * CASE 表达式可以简化，如果存在常量
				 * 条件子句：
				 *		FALSE（或 NULL）：丢弃该替代
				 *		TRUE：丢弃所有剩余的替代
				 * 如果第一个非-FALSE 的替代是常量 TRUE，
				 * 我们可以将整个 CASE 简化为该替代的
				 * 表达式。如果没有非-FALSE 的替代，
				 * 我们将整个 CASE 简化为默认结果（ELSE）。
				 *
				 * 如果我们有一个简单形式的 CASE 及常量测试
				 * 表达式，我们会将常量值替换为包含的
				 * CaseTestExpr 占位节点，以便有机会减少常量测试条件。
				 * 例如，这允许
				 *		CASE 0 WHEN 0 THEN 1 ELSE 1/0 END
				 * 简化为 1，而不是引发除以 0 的错误。
				 * 注意，当测试表达式是常量时，我们不
				 * 必须将其包含在结果 CASE 中；例如
				 *		CASE 0 WHEN x THEN y ELSE z END
				 * 会被解析器转化为
				 *		CASE 0 WHEN CaseTestExpr = x THEN y ELSE z END
				 * 我们可以简化为
				 *		CASE WHEN 0 = x THEN y ELSE z END
				 * 执行器在执行 CASE 时不需要评估“arg”
				 * 表达式，因为任何可能引用它的
				 * CaseTestExpr 都将被常量替换。
				 *----------
				 */
				CaseExpr   *fc_caseexpr = (CaseExpr *) fc_node;
				CaseExpr   *fc_newcase;
				Node	   *fc_save_case_val;
				Node	   *fc_newarg;
				List	   *fc_newargs;
				bool		fc_const_true_cond;
				Node	   *fc_defresult = NULL;
				ListCell   *fc_arg;

				/* 简化测试表达式（如果有的话） */
				fc_newarg = fc_eval_const_expressions_mutator((Node *) fc_caseexpr->arg,
														fc_context);

				/* 为包含的 CaseTestExpr 节点做准备 */
				fc_save_case_val = fc_context->case_val;
				if (fc_newarg && IsA(fc_newarg, Const))
				{
					fc_context->case_val = fc_newarg;
					fc_newarg = NULL;	/* 不再需要，见上文 */
				}
				else
					fc_context->case_val = NULL;

				/* 简化 WHEN 条件 */
				fc_newargs = NIL;
				fc_const_true_cond = false;
				foreach(fc_arg, fc_caseexpr->args)
				{
					CaseWhen   *fc_oldcasewhen = lfirst_node(CaseWhen, fc_arg);
					Node	   *fc_casecond;
					Node	   *fc_caseresult;

					/* 简化该替代的测试条件 */
					fc_casecond = fc_eval_const_expressions_mutator((Node *) fc_oldcasewhen->expr,
															  fc_context);

					/*
					 * 如果测试条件是常量 FALSE（或 NULL），
					 * 则完全丢弃该 WHEN 子句，而不处理
					 * 结果。
					 */
					if (fc_casecond && IsA(fc_casecond, Const))
					{
						Const	   *fc_const_input = (Const *) fc_casecond;

						if (fc_const_input->constisnull ||
							!DatumGetBool(fc_const_input->constvalue))
							continue;	/* 丢弃具有 FALSE 条件的替代 */
						/* 否则它是常量 TRUE */
						fc_const_true_cond = true;
					}

					/* 简化该替代的结果值 */
					fc_caseresult = fc_eval_const_expressions_mutator((Node *) fc_oldcasewhen->result,
																fc_context);

					/* 如果测试条件非恒定，则发出一个新的 WHEN 节点 */
					if (!fc_const_true_cond)
					{
						CaseWhen   *fc_newcasewhen = makeNode(CaseWhen);

						fc_newcasewhen->expr = (Expr *) fc_casecond;
						fc_newcasewhen->result = (Expr *) fc_caseresult;
						fc_newcasewhen->location = fc_oldcasewhen->location;
						fc_newargs = lappend(fc_newargs, fc_newcasewhen);
						continue;
					}

					/*
					 * 找到一个 TRUE 条件，所以无法到达其余
					 * 替代。我们将结果视为默认结果。
					 */
					fc_defresult = fc_caseresult;
					break;
				}

				/* 简化默认结果，除非我们在上文中替换了它 */
				if (!fc_const_true_cond)
					fc_defresult = fc_eval_const_expressions_mutator((Node *) fc_caseexpr->defresult,
															   fc_context);

				fc_context->case_val = fc_save_case_val;

				/*
				 * 如果没有非-FALSE 替代，CASE 简化为默认
				 * 结果
				 */
				if (fc_newargs == NIL)
					return fc_defresult;
				/* 否则我们需要一个新的 CASE 节点 */
				fc_newcase = makeNode(CaseExpr);
				fc_newcase->casetype = fc_caseexpr->casetype;
				fc_newcase->casecollid = fc_caseexpr->casecollid;
				fc_newcase->arg = (Expr *) fc_newarg;
				fc_newcase->args = fc_newargs;
				fc_newcase->defresult = (Expr *) fc_defresult;
				fc_newcase->location = fc_caseexpr->location;
				return (Node *) fc_newcase;
			}
		case T_CaseTestExpr:
			{
				/*
				 * 如果我们知道当前 CASE 构造的常量测试值，
				 * 则将其替换为占位符。否则只需
				 * 原样返回占位符。
				 */
				if (fc_context->case_val)
					return copyObject(fc_context->case_val);
				else
					return copyObject(fc_node);
			}
		case T_SubscriptingRef:
		case T_ArrayExpr:
		case T_RowExpr:
		case T_MinMaxExpr:
			{
				/*
				 * 对于自身处理已知是不可变的节点类型的通用处理，
				 * 我们不需要的聪明才智仅仅是“如果所有输入都是常量，则简化”。
				 *
				 * 以这种方式对 SubscriptingRef 的处理假设在下标
				 * 获取和赋值都是不可变的。这约束了特定类型的下标
				 * 实现；也许我们有一天应该放宽它。
				 *
				 * 以这种方式对 MinMaxExpr 的处理意味着假设它调用的
				 * btree 比较函数是不可变的；见 contain_mutable_functions_walker 中的推理。
				 */

				/* 复制节点并常量简化其参数 */
				fc_node = ece_generic_processing(fc_node);
				/* 如果所有参数都是 Consts，我们可以归并为常量 */
				if (ece_all_arguments_const(fc_node))
					return ece_evaluate_expr(fc_node);
				return fc_node;
			}
		case T_CoalesceExpr:
			{
				CoalesceExpr *fc_coalesceexpr = (CoalesceExpr *) fc_node;
				CoalesceExpr *fc_newcoalesce;
				List	   *fc_newargs;
				ListCell   *fc_arg;

				fc_newargs = NIL;
				foreach(fc_arg, fc_coalesceexpr->args)
				{
					Node	   *fc_e;

					fc_e = fc_eval_const_expressions_mutator((Node *) lfirst(fc_arg),
													   fc_context);

					/*
					 * 我们可以从列表中移除空常量。对于一个
					 * 非空常量，如果它没有被任何其他
					 * 非空常量表达式所前置，那么它就是结果。
					 * 否则，它就是下一个参数，但我们可以
					 * 丢弃后续参数，因为它们永远不会被
					 * 到达。
					 */
					if (IsA(fc_e, Const))
					{
						if (((Const *) fc_e)->constisnull)
							continue;	/* 丢弃空常量 */
						if (fc_newargs == NIL)
							return fc_e;	/* 第一个表达式 */
						fc_newargs = lappend(fc_newargs, fc_e);
						break;
					}
					fc_newargs = lappend(fc_newargs, fc_e);
				}

				/*
				 * 如果所有参数都是常量空，结果就是
				 * 空
				 */
				if (fc_newargs == NIL)
					return (Node *) makeNullConst(fc_coalesceexpr->coalescetype,
												  -1,
												  fc_coalesceexpr->coalescecollid);

				fc_newcoalesce = makeNode(CoalesceExpr);
				fc_newcoalesce->coalescetype = fc_coalesceexpr->coalescetype;
				fc_newcoalesce->coalescecollid = fc_coalesceexpr->coalescecollid;
				fc_newcoalesce->args = fc_newargs;
				fc_newcoalesce->location = fc_coalesceexpr->location;
				return (Node *) fc_newcoalesce;
			}
		case T_SQLValueFunction:
			{
				/*
				 * 所有 SQLValueFunction 的变体都是稳定的，
				 * 所以如果我们正在估算表达式的值，我们应该
				 * 评估当前函数值。否则，只需复制。
				 */
				SQLValueFunction *fc_svf = (SQLValueFunction *) fc_node;

				if (fc_context->estimate)
					return (Node *) evaluate_expr((Expr *) fc_svf,
												  fc_svf->type,
												  fc_svf->typmod,
												  InvalidOid);
				else
					return copyObject((Node *) fc_svf);
			}
		case T_FieldSelect:
			{
				
/*
				 * 我们可以将整行变量的字段选择优化为一个简单的变量。
				 * （此情况不会直接由解析器生成，因为ParseComplexProjection会短路处理它。
				 * 但在简化函数时会出现。这也适用于从RowExpr构造中优化字段选择，
				 * 当然也可以从常量中选择。）
				 *
				 * 然而，以这种方式替换整行变量有一个陷阱：如果我们已经为源关系构建了关系目标列表，
				 * 那么整行变量是由关系扫描生成的，但简单变量可能不是，
				 * 这将导致setrefs.c中的失败。在处理简单的单层查询时这不是问题，
				 * 因为表达式简化总是首先发生。然而对于来自子查询的横向引用来说这是一个风险。
				 * 为了避免这种失败，别优化上层引用。
				 *
				 * 我们还必须检查字段的声明类型是否仍然与创建FieldSelect时相同——
				 * 如果有人对行类型执行了ALTER COLUMN TYPE，这可能会改变。
				 * 如果不一样，我们将跳过优化；这个案例可能会在运行时失败，但这不是我们这里的问题。
				 */
				FieldSelect *fc_fselect = (FieldSelect *) fc_node;
				FieldSelect *fc_newfselect;
				Node	   *fc_arg;

				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_fselect->arg,
													 fc_context);
				if (fc_arg && IsA(fc_arg, Var) &&
					((Var *) fc_arg)->varattno == InvalidAttrNumber &&
					((Var *) fc_arg)->varlevelsup == 0)
				{
					if (fc_rowtype_field_matches(((Var *) fc_arg)->vartype,
											  fc_fselect->fieldnum,
											  fc_fselect->resulttype,
											  fc_fselect->resulttypmod,
											  fc_fselect->resultcollid))
						return (Node *) makeVar(((Var *) fc_arg)->varno,
												fc_fselect->fieldnum,
												fc_fselect->resulttype,
												fc_fselect->resulttypmod,
												fc_fselect->resultcollid,
												((Var *) fc_arg)->varlevelsup);
				}
				if (fc_arg && IsA(fc_arg, RowExpr))
				{
					RowExpr    *fc_rowexpr = (RowExpr *) fc_arg;

					if (fc_fselect->fieldnum > 0 &&
						fc_fselect->fieldnum <= list_length(fc_rowexpr->args))
					{
						Node	   *fc_fld = (Node *) list_nth(fc_rowexpr->args,
															fc_fselect->fieldnum - 1);

						if (fc_rowtype_field_matches(fc_rowexpr->row_typeid,
												  fc_fselect->fieldnum,
												  fc_fselect->resulttype,
												  fc_fselect->resulttypmod,
												  fc_fselect->resultcollid) &&
							fc_fselect->resulttype == exprType(fc_fld) &&
							fc_fselect->resulttypmod == exprTypmod(fc_fld) &&
							fc_fselect->resultcollid == exprCollation(fc_fld))
							return fc_fld;
					}
				}
				fc_newfselect = makeNode(FieldSelect);
				fc_newfselect->arg = (Expr *) fc_arg;
				fc_newfselect->fieldnum = fc_fselect->fieldnum;
				fc_newfselect->resulttype = fc_fselect->resulttype;
				fc_newfselect->resulttypmod = fc_fselect->resulttypmod;
				fc_newfselect->resultcollid = fc_fselect->resultcollid;
				if (fc_arg && IsA(fc_arg, Const))
				{
					Const	   *fc_con = (Const *) fc_arg;

					if (fc_rowtype_field_matches(fc_con->consttype,
											  fc_newfselect->fieldnum,
											  fc_newfselect->resulttype,
											  fc_newfselect->resulttypmod,
											  fc_newfselect->resultcollid))
						return ece_evaluate_expr(fc_newfselect);
				}
				return (Node *) fc_newfselect;
			}
		case T_NullTest:
			{
				NullTest   *fc_ntest = (NullTest *) fc_node;
				NullTest   *fc_newntest;
				Node	   *fc_arg;

				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_ntest->arg,
													 fc_context);
				if (fc_ntest->argisrow && fc_arg && IsA(fc_arg, RowExpr))
				{
					/*
					 * 我们将ROW(...) IS [NOT] NULL分解为对其组成字段的单独测试。
					 * 这种形式通常更高效评估，并且更便于优化。
					 */
					RowExpr    *fc_rarg = (RowExpr *) fc_arg;
					List	   *fc_newargs = NIL;
					ListCell   *fc_l;

					foreach(fc_l, fc_rarg->args)
					{
						Node	   *fc_relem = (Node *) lfirst(fc_l);

						/*
						 * 如果常量字段的空值性不正确，它将否定整个NullTest；
						 * 否则我们可以丢弃它。
						 */
						if (fc_relem && IsA(fc_relem, Const))
						{
							Const	   *fc_carg = (Const *) fc_relem;

							if (fc_carg->constisnull ?
								(fc_ntest->nulltesttype == IS_NOT_NULL) :
								(fc_ntest->nulltesttype == IS_NULL))
								return makeBoolConst(false, false);
							continue;
						}

						/*
						 * 否则，为这个字段创建一个标量（argisrow == false）NullTest。
						 * 需要标量语义，因为IS [NOT] NULL不递归；请参见ExecEvalRowNullInt()中的注释。
						 */
						fc_newntest = makeNode(NullTest);
						fc_newntest->arg = (Expr *) fc_relem;
						fc_newntest->nulltesttype = fc_ntest->nulltesttype;
						fc_newntest->argisrow = false;
						fc_newntest->location = fc_ntest->location;
						fc_newargs = lappend(fc_newargs, fc_newntest);
					}
					/* 如果所有输入都是常量，结果为TRUE */
					if (fc_newargs == NIL)
						return makeBoolConst(true, false);
					/* 如果只有一个非常量输入，就是结果 */
					if (list_length(fc_newargs) == 1)
						return (Node *) linitial(fc_newargs);
					/* 否则我们需要一个与节点 */
					return (Node *) make_andclause(fc_newargs);
				}
				if (!fc_ntest->argisrow && fc_arg && IsA(fc_arg, Const))
				{
					Const	   *fc_carg = (Const *) fc_arg;
					bool		fc_result;

					switch (fc_ntest->nulltesttype)
					{
						case IS_NULL:
							fc_result = fc_carg->constisnull;
							break;
						case IS_NOT_NULL:
							fc_result = !fc_carg->constisnull;
							break;
						default:
							elog(ERROR, "unrecognized nulltesttype: %d",
								 (int) fc_ntest->nulltesttype);
							fc_result = false; /* 保持编译器安静 */
							break;
					}

					return makeBoolConst(fc_result, false);
				}

				fc_newntest = makeNode(NullTest);
				fc_newntest->arg = (Expr *) fc_arg;
				fc_newntest->nulltesttype = fc_ntest->nulltesttype;
				fc_newntest->argisrow = fc_ntest->argisrow;
				fc_newntest->location = fc_ntest->location;
				return (Node *) fc_newntest;
			}
		case T_BooleanTest:
			{
				/*
				 * 此情况可以折叠到用于 ArrayExpr 等的通用处理。
				 * 但是因为简化逻辑非常简单，应用 evaluate_expr() 来执行它将
				 * 产生较大的开销。BooleanTest 可能常见到足以
				 * 证明保留这一专门实现是合理的。
				 */
				BooleanTest *fc_btest = (BooleanTest *) fc_node;
				BooleanTest *fc_newbtest;
				Node	   *fc_arg;

				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_btest->arg,
													 fc_context);
				if (fc_arg && IsA(fc_arg, Const))
				{
					Const	   *fc_carg = (Const *) fc_arg;
					bool		fc_result;

					switch (fc_btest->booltesttype)
					{
						case IS_TRUE:
							fc_result = (!fc_carg->constisnull &&
									  DatumGetBool(fc_carg->constvalue));
							break;
						case IS_NOT_TRUE:
							fc_result = (fc_carg->constisnull ||
									  !DatumGetBool(fc_carg->constvalue));
							break;
						case IS_FALSE:
							fc_result = (!fc_carg->constisnull &&
									  !DatumGetBool(fc_carg->constvalue));
							break;
						case IS_NOT_FALSE:
							fc_result = (fc_carg->constisnull ||
									  DatumGetBool(fc_carg->constvalue));
							break;
						case IS_UNKNOWN:
							fc_result = fc_carg->constisnull;
							break;
						case IS_NOT_UNKNOWN:
							fc_result = !fc_carg->constisnull;
							break;
						default:
							elog(ERROR, "unrecognized booltesttype: %d",
								 (int) fc_btest->booltesttype);
							fc_result = false; /* 保持编译器安静 */
							break;
					}

					return makeBoolConst(fc_result, false);
				}

				fc_newbtest = makeNode(BooleanTest);
				fc_newbtest->arg = (Expr *) fc_arg;
				fc_newbtest->booltesttype = fc_btest->booltesttype;
				fc_newbtest->location = fc_btest->location;
				return (Node *) fc_newbtest;
			}
		case T_CoerceToDomain:
			{
				/*
				 * 如果当前域没有约束，我们用一个简单的 RelabelType 替换
				 * CoerceToDomain 节点，这在执行速度上更快，也更易于后续
				 * 优化。然后我们必须标记计划为需要重构，如果域的约束发生变化。
				 *
				 * 此外，在估算模式下，总是替换 CoerceToDomain
				 * 节点，实际上假定强制转换将成功。
				 */
				CoerceToDomain *fc_cdomain = (CoerceToDomain *) fc_node;
				CoerceToDomain *fc_newcdomain;
				Node	   *fc_arg;

				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_cdomain->arg,
													 fc_context);
				if (fc_context->estimate ||
					!DomainHasConstraints(fc_cdomain->resulttype))
				{
					/* 如果这不是估算模式，记录依赖关系 */
					if (fc_context->root && !fc_context->estimate)
						record_plan_type_dependency(fc_context->root,
													fc_cdomain->resulttype);

					/* 生成 RelabelType 以替代 CoerceToDomain */
					return applyRelabelType(fc_arg,
											fc_cdomain->resulttype,
											fc_cdomain->resulttypmod,
											fc_cdomain->resultcollid,
											fc_cdomain->coercionformat,
											fc_cdomain->location,
											true);
				}

				fc_newcdomain = makeNode(CoerceToDomain);
				fc_newcdomain->arg = (Expr *) fc_arg;
				fc_newcdomain->resulttype = fc_cdomain->resulttype;
				fc_newcdomain->resulttypmod = fc_cdomain->resulttypmod;
				fc_newcdomain->resultcollid = fc_cdomain->resultcollid;
				fc_newcdomain->coercionformat = fc_cdomain->coercionformat;
				fc_newcdomain->location = fc_cdomain->location;
				return (Node *) fc_newcdomain;
			}
		case T_PlaceHolderVar:

			/*
			 * 在估算模式下，直接去掉 PlaceHolderVar 节点；这相当于估算
			 * 被包含值不会被外部连接强制为 NULL。在常规模式下，我们
			 * 只需使用默认行为（即简化表达式但保持 PlaceHolderVar 节点不变）。
			 */
			if (fc_context->estimate)
			{
				PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

				return fc_eval_const_expressions_mutator((Node *) fc_phv->phexpr,
													  fc_context);
			}
			break;
		case T_ConvertRowtypeExpr:
			{
				ConvertRowtypeExpr *fc_cre = castNode(ConvertRowtypeExpr, fc_node);
				Node	   *fc_arg;
				ConvertRowtypeExpr *fc_newcre;

				fc_arg = fc_eval_const_expressions_mutator((Node *) fc_cre->arg,
													 fc_context);

				fc_newcre = makeNode(ConvertRowtypeExpr);
				fc_newcre->resulttype = fc_cre->resulttype;
				fc_newcre->convertformat = fc_cre->convertformat;
				fc_newcre->location = fc_cre->location;

				/*
				 * 在嵌套的 ConvertRowtypeExpr 的情况下，我们可以将
				 * 叶行直接转换为最上面的行格式，而无需任何
				 * 中间转换。 （之所以有效，是因为 ConvertRowtypeExpr 仅用于
				 * 子->父转换，并通过列名的精确匹配进行，这样中间结果中
				 * 缺少的列无法出现在最终结果中。）
				 *
				 * 只需检查一层深度，因为上述递归将扁平化其他所有内容。
				 */
				if (fc_arg != NULL && IsA(fc_arg, ConvertRowtypeExpr))
				{
					ConvertRowtypeExpr *fc_argcre = (ConvertRowtypeExpr *) fc_arg;

					fc_arg = (Node *) fc_argcre->arg;

					/*
					 * 确保外部隐式转换不会隐藏内部显式转换。
					 */
					if (fc_newcre->convertformat == COERCE_IMPLICIT_CAST)
						fc_newcre->convertformat = fc_argcre->convertformat;
				}

				fc_newcre->arg = (Expr *) fc_arg;

				if (fc_arg != NULL && IsA(fc_arg, Const))
					return ece_evaluate_expr((Node *) fc_newcre);
				return (Node *) fc_newcre;
			}
		default:
			break;
	}

	/*
	 * 对于任何未处理的节点类型，复制该节点不变，但将其子表达式进行常量简化。
	 * 这是正确的做法，对于在计划和执行之间可能会改变行为的节点
	 * 类型，例如 CurrentOfExpr。这也是对本例程不熟悉的新节点类型的安全默认值。
	 */
	return ece_generic_processing(fc_node);
}

/*
 * eval_const_expressions 的子例程：检查非 Const 节点。
 *
 * 我们在发现非 Const 节点时可以立即中止递归。这对性能至关重要，
 * 否则 eval_const_expressions_mutator 在不可简化的树上将需要
 * O(N^2) 的时间。然而，我们确实需要进入 List 节点，因为 expression_tree_walker
 * 有时会直接在 List 子树上调用 walker 函数。
 */
static bool fc_contain_non_const_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Const))
		return false;
	if (IsA(fc_node, List))
		return expression_tree_walker(fc_node, fc_contain_non_const_walker, fc_context);
	/* 否则，终止树遍历并返回 true */
	return true;
}

/*
 * eval_const_expressions 的子例程：检查函数是否可以评估
 */
static bool fc_ece_function_is_safe(Oid fc_funcid, eval_const_expressions_context *fc_context)
{
	char		fc_provolatile = func_volatile(fc_funcid);

	/*
	 * 通常我们只允许简化不可变函数。但出于估算目的，我们认为简化
	 * 仅仅是稳定的函数也是可以的；因为结果可能在规划时间与执行时间之间变化
	 * 的风险值得一试，而不完全无法估算这些值。
	 */
	if (fc_provolatile == PROVOLATILE_IMMUTABLE)
		return true;
	if (fc_context->estimate && fc_provolatile == PROVOLATILE_STABLE)
		return true;
	return false;
}

/*
 * eval_const_expressions 的子例程：处理 OR 子句的参数
 *
 * 这包括将嵌套的 OR 扁平化，以及递归调用 eval_const_expressions
 * 来简化 OR 参数。
 *
 * 在简化之后，OR 参数的处理如下：
 *		非常量：保留
 *		FALSE：丢弃（不影响结果）
 *		TRUE：强制结果为 TRUE
 *		NULL：仅保留一个
 * 我们必须保持一个 NULL 输入，因为当没有输入为 TRUE 且至少一个为 NULL 时，OR 表达式评估为 NULL。
 * 我们实际上不会将 NULL 包含在此处，这应该由调用者来处理。
 *
 * 输出参数 *haveNull 和 *forceTrue 必须由调用者初始化为 false。
 * 如果在参数列表的任何地方检测到 NULL 常量或 TRUE 常量，它们将被设置为 true。
 */
static List * fc_simplify_or_arguments(List *fc_args,
					  eval_const_expressions_context *fc_context,
					  bool *fc_haveNull, bool *fc_forceTrue)
{
	List	   *fc_newargs = NIL;
	List	   *fc_unprocessed_args;

	/*
	 * 我们想确保任何位于另一个 OR 之下的 OR 立即被扁平化为一个单一的 OR 列表，
	 * 以简化后续推理。
	 *
	 * 为了避免 eval_const_expressions 的递归导致堆栈溢出，我们这里采取了一些紧张性：
	 * 我们保持一个尚未处理的输入列表，并通过在待处理列表的前面添加来处理
	 * 嵌套 OR 的扁平化，而不是递归。现在解析器从简单列表生成 N-参数
	 * OR ，这种复杂性可能不如以前那样必要，但我们也不妨保留这个逻辑。
	 */
	fc_unprocessed_args = list_copy(fc_args);
	while (fc_unprocessed_args)
	{
		Node	   *fc_arg = (Node *) linitial(fc_unprocessed_args);

		fc_unprocessed_args = list_delete_first(fc_unprocessed_args);

		
/* 根据上述评论展平嵌套的 OR */
		if (is_orclause(fc_arg))
		{
			List	   *fc_subargs = ((BoolExpr *) fc_arg)->args;
			List	   *fc_oldlist = fc_unprocessed_args;

			fc_unprocessed_args = list_concat_copy(fc_subargs, fc_unprocessed_args);
			/* 可能过于紧张的代码以避免泄漏旧列表 */
			list_free(fc_oldlist);
			continue;
		}

		/* 如果它不是一个 OR，则简化它 */
		fc_arg = fc_eval_const_expressions_mutator(fc_arg, fc_context);

		/*
		 * 尽管不太可能，但简化一个非 OR 子句可能会产生一个 OR。
		 * 再次检查，但不要对此过于紧张，因为这不是主流案例。
		 * 特别是我们不担心对输入进行两次常量简化，也不担心列表泄漏。
		 */
		if (is_orclause(fc_arg))
		{
			List	   *fc_subargs = ((BoolExpr *) fc_arg)->args;

			fc_unprocessed_args = list_concat_copy(fc_subargs, fc_unprocessed_args);
			continue;
		}

		/*
		 * 好的，我们有一个常量简化的非 OR 参数。根据
		 * 上述评论进行处理。
		 */
		if (IsA(fc_arg, Const))
		{
			Const	   *fc_const_input = (Const *) fc_arg;

			if (fc_const_input->constisnull)
				*fc_haveNull = true;
			else if (DatumGetBool(fc_const_input->constvalue))
			{
				*fc_forceTrue = true;

				/*
				 * 一旦我们检测到 TRUE 结果，我们可以立即退出循环。
				 * 然而，如果我们增加了不可移除函数的概念，我们需要继续扫描。
				 */
				return NIL;
			}
			/* 否则，我们可以舍弃常量假输入 */
			continue;
		}

		/* 否则将简化的参数输出到结果列表中 */
		fc_newargs = lappend(fc_newargs, fc_arg);
	}

	return fc_newargs;
}

/*
 * eval_const_expressions 的子例程：处理 AND 子句的参数
 *
 * 这包括展平嵌套的 AND 以及递归到
 * eval_const_expressions 以简化 AND 参数。
 *
 * 简化后，AND 参数按如下处理：
 *		非常量：保持
 *		TRUE：丢弃（不影响结果）
 *		FALSE：强制结果为 FALSE
 *		NULL：只保持一个
 * 我们必须保留一个 NULL 输入，因为当没有输入为 FALSE 且至少一个为 NULL 时，AND 表达式会评估为 NULL。我们实际上不包括 NULL，这应由调用者完成。
 *
 * 输出参数 *haveNull 和 *forceFalse 必须由调用者初始化为 false。
 * 如果在参数列表中检测到 NULL 常量或 FALSE 常量，它们将被设置为 true。
 */
static List * fc_simplify_and_arguments(List *fc_args,
					   eval_const_expressions_context *fc_context,
					   bool *fc_haveNull, bool *fc_forceFalse)
{
	List	   *fc_newargs = NIL;
	List	   *fc_unprocessed_args;

	/* 请参见 simplify_or_arguments 中的评论 */
	fc_unprocessed_args = list_copy(fc_args);
	while (fc_unprocessed_args)
	{
		Node	   *fc_arg = (Node *) linitial(fc_unprocessed_args);

		fc_unprocessed_args = list_delete_first(fc_unprocessed_args);

		/* 根据上述评论展平嵌套的 AND */
		if (is_andclause(fc_arg))
		{
			List	   *fc_subargs = ((BoolExpr *) fc_arg)->args;
			List	   *fc_oldlist = fc_unprocessed_args;

			fc_unprocessed_args = list_concat_copy(fc_subargs, fc_unprocessed_args);
			/* 可能过于紧张的代码以避免泄漏旧列表 */
			list_free(fc_oldlist);
			continue;
		}

		/* 如果它不是一个 AND，则简化它 */
		fc_arg = fc_eval_const_expressions_mutator(fc_arg, fc_context);

		/*
		 * 尽管不太可能，但简化一个非 AND 子句可能会产生一个 AND。
		 * 再次检查，但不要对此过于紧张，因为这不是主流案例。
		 * 特别是我们不担心对输入进行两次常量简化，也不担心列表泄漏。
		 */ 
		if (is_andclause(fc_arg))
		{
			List	   *fc_subargs = ((BoolExpr *) fc_arg)->args;

			fc_unprocessed_args = list_concat_copy(fc_subargs, fc_unprocessed_args);
			continue;
		}

		/*
		 * 好的，我们有一个常量简化的非与（AND）参数。根据上面的注释处理它。
		 */
		if (IsA(fc_arg, Const))
		{
			Const	   *fc_const_input = (Const *) fc_arg;

			if (fc_const_input->constisnull)
				*fc_haveNull = true;
			else if (!DatumGetBool(fc_const_input->constvalue))
			{
				*fc_forceFalse = true;

				/*
				 * 一旦我们检测到一个 FALSE 结果，我们可以立即退出循环。
				 * 但是，如果我们添加了不可移除函数的概念，我们就需要继续扫描。
				 */
				return NIL;
			}
			/* 否则，我们可以丢弃常量为真的输入 */
			continue;
		}

		/* 否则将简化的参数输出到结果列表中 */
		fc_newargs = lappend(fc_newargs, fc_arg);
	}

	return fc_newargs;
}

/*
 * eval_const_expressions 的子例程：尝试简化布尔相等
 * 或不等条件
 *
 * 输入是操作符 OID 和操作符的简化参数。
 * 如果成功，返回简化表达式；如果无法简化表达式，则返回 NULL。
 *
 * 这里的想法是将 "x = true" 简化为 "x"，将 "x = false" 简化为 "NOT x"， 
 * 或类似地将 "x <> true" 简化为 "NOT x"，将 "x <> false" 简化为 "x"。
 * 这本身的用处有限，但在常量折叠中执行它可以确保我们会识别这些形式在，例如，
 * 部分索引匹配中是等价的。
 *
 * 我们只有在 simplify_function 失败的情况下到达这里；因此我们不能看到两个常量输入，
 * 也不能看到常量 NULL 输入。
 */
static Node * fc_simplify_boolean_equality(Oid fc_opno, List *fc_args)
{
	Node	   *fc_leftop;
	Node	   *fc_rightop;

	Assert(list_length(fc_args) == 2);
	fc_leftop = linitial(fc_args);
	fc_rightop = lsecond(fc_args);
	if (fc_leftop && IsA(fc_leftop, Const))
	{
		Assert(!((Const *) fc_leftop)->constisnull);
		if (fc_opno == BooleanEqualOperator)
		{
			if (DatumGetBool(((Const *) fc_leftop)->constvalue))
				return fc_rightop; /* true = foo */
			else
				return negate_clause(fc_rightop);	/* false = foo */
		}
		else
		{
			if (DatumGetBool(((Const *) fc_leftop)->constvalue))
				return negate_clause(fc_rightop);	/* true <> foo */
			else
				return fc_rightop; /* false <> foo */
		}
	}
	if (fc_rightop && IsA(fc_rightop, Const))
	{
		Assert(!((Const *) fc_rightop)->constisnull);
		if (fc_opno == BooleanEqualOperator)
		{
			if (DatumGetBool(((Const *) fc_rightop)->constvalue))
				return fc_leftop;	/* foo = true */
			else
				return negate_clause(fc_leftop);	/* foo = false */
		}
		else
		{
			if (DatumGetBool(((Const *) fc_rightop)->constvalue))
				return negate_clause(fc_leftop);	/* foo <> true */
			else
				return fc_leftop;	/* foo <> false */
		}
	}
	return NULL;
}

/*
 * eval_const_expressions 的子例程：尝试简化函数调用
 * （这可能最初是一个操作符；我们不在乎）
 *
 * 输入是函数 OID、实际结果类型 OID（对于多态函数需要此项）、结果 typmod、
 * 结果排序、用于函数的输入排序、原始参数列表（尚未常量简化，除非 process_args 为 false）
 * 和一些标志；此外，还有 eval_const_expressions 的上下文数据。
 *
 * 如果成功，返回简化表达式；如果无法简化函数调用，则返回 NULL。
 *
 * 此函数还负责将命名标记的参数列表转换为位置标记，并/或添加任何需要的默认参数
 * 表达式；这有点麻烦，但可以避免额外获取函数的 pg_proc 元组。出于这个原因，参数列表是
 * 通过引用传递的。即使无法简化函数调用本身，参数列表的转换和常量简化仍会进行。
 */
static Expr * fc_simplify_function(Oid fc_funcid, Oid fc_result_type, int32 fc_result_typmod,
				  Oid fc_result_collid, Oid fc_input_collid, List **fc_args_p,
				  bool fc_funcvariadic, bool fc_process_args, bool fc_allow_non_const,
				  eval_const_expressions_context *fc_context)
{
	List	   *fc_args = *fc_args_p;
	HeapTuple	fc_func_tuple;
	Form_pg_proc fc_func_form;
	Expr	   *fc_newexpr;

	/*
	 * 我们有三种简化策略：执行函数以提供常量结果，使用转换函数生成
	 * 替代节点树，或内联扩展函数定义的主体（这仅适用于简单 SQL 语言函数，但
	 * 这是一个常见案例）。每种情况都需要访问函数的 pg_proc 元组，因此只需获取一次。
	 *
	 * 注意：allow_non_const 标志抑制第二和第三种策略；因此，如果 !allow_non_const，
	 * simplify_function 只能返回 Const 或 NULL。尽管如此，参数列表重写还是会进行。
	 */
	fc_func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_func_tuple))
		elog(ERROR, "cache lookup failed for function %u", fc_funcid);
	fc_func_form = (Form_pg_proc) GETSTRUCT(fc_func_tuple);

	/*
	 * 处理函数参数，除非调用者已经处理过。
	 *
	 * 这里我们必须处理命名或默认参数，然后递归应用 eval_const_expressions 到整个参数列表。
	 */
	if (fc_process_args)
	{
		fc_args = expand_function_arguments(fc_args, false, fc_result_type, fc_func_tuple);
		fc_args = (List *) expression_tree_mutator((Node *) fc_args,
												fc_eval_const_expressions_mutator,
												(void *) fc_context);
		/* 参数处理完成，返还给调用者 */
		*fc_args_p = fc_args;
	}

	/* 现在尝试简化函数调用本身。 */

	fc_newexpr = fc_evaluate_function(fc_funcid, fc_result_type, fc_result_typmod,
								fc_result_collid, fc_input_collid,
								fc_args, fc_funcvariadic,
								fc_func_tuple, fc_context);

	if (!fc_newexpr && fc_allow_non_const && OidIsValid(fc_func_form->prosupport))
	{
		/*
		 * 创建一个 SupportRequestSimplify 节点传递给支持
		 * 函数，指向一个包含简化参数列表的虚拟 FuncExpr 节点。我们使用这种方法为支持函数
		 * 提供一个统一的接口，而不管目标函数实际上是如何被调用的。
		 */
		SupportRequestSimplify fc_req;
		FuncExpr	fc_fexpr;

		fc_fexpr.xpr.type = T_FuncExpr;
		fc_fexpr.funcid = fc_funcid;
		fc_fexpr.funcresulttype = fc_result_type;
		fc_fexpr.funcretset = fc_func_form->proretset;
		fc_fexpr.funcvariadic = fc_funcvariadic;
		fc_fexpr.funcformat = COERCE_EXPLICIT_CALL;
		fc_fexpr.funccollid = fc_result_collid;
		fc_fexpr.inputcollid = fc_input_collid;
		fc_fexpr.args = fc_args;
		fc_fexpr.location = -1;

		fc_req.type = T_SupportRequestSimplify;
		fc_req.root = fc_context->root;
		fc_req.fcall = &fc_fexpr;

		fc_newexpr = (Expr *)
			DatumGetPointer(OidFunctionCall1(fc_func_form->prosupport,
											 PointerGetDatum(&fc_req)));

		/* 捕获可能的 API 误解 */
		Assert(fc_newexpr != (Expr *) &fc_fexpr);
	}

	if (!fc_newexpr && fc_allow_non_const)
		fc_newexpr = fc_inline_function(fc_funcid, fc_result_type, fc_result_collid,
								  fc_input_collid, fc_args, fc_funcvariadic,
								  fc_func_tuple, fc_context);

	ReleaseSysCache(fc_func_tuple);

	return fc_newexpr;
}

/*
 * expand_function_arguments：将命名标记参数转换为位置参数
 * 和/或必要时插入默认参数
 *
 * 返回可能转化过的参数列表版本。
 *
 * 如果 include_out_arguments 为 true，则参数列表和结果包含 OUT 参数。
 *
 * 调用的预期结果类型必须提供，以进行合理性检查。
 * 此外，我们要求调用者提供函数实际的 pg_proc 元组，不仅仅是其 OID。
 *
 * 如果需要更改任何内容，则输入参数列表将被复制，而不是修改。
 *
 * 注意：这同样适用于操作符参数列表，尽管它处理的案例在这里应该永远不会发生。
 * 这应该没问题，因为如果没有任何事情要处理，它会非常快地跳过。
 */
List * expand_function_arguments(List *fc_args, bool fc_include_out_arguments,
						  Oid fc_result_type, HeapTuple fc_func_tuple)
{
	Form_pg_proc fc_funcform = (Form_pg_proc) GETSTRUCT(fc_func_tuple);
	Oid		   *fc_proargtypes = fc_funcform->proargtypes.values;
	int			fc_pronargs = fc_funcform->pronargs;
	bool		fc_has_named_args = false;
	ListCell   *fc_lc;

	
/*
	 * 如果我们被要求匹配到 OUT 参数，则使用 proallargtypes
	 * 数组（它包含那些参数）；否则使用 proargtypes（它不包含）。当然，如果 proallargtypes 为 null，我们始终使用
	 * proargtypes。（获取 proallargtypes 的成本令人烦恼，因为我们可能在这里没有任何事情要做，但幸运的是，常见情况是 include_out_arguments == false。）
	 */
	if (fc_include_out_arguments)
	{
		Datum		fc_proallargtypes;
		bool		fc_isNull;

		fc_proallargtypes = SysCacheGetAttr(PROCOID, fc_func_tuple,
										 Anum_pg_proc_proallargtypes,
										 &fc_isNull);
		if (!fc_isNull)
		{
			ArrayType  *fc_arr = DatumGetArrayTypeP(fc_proallargtypes);

			fc_pronargs = ARR_DIMS(fc_arr)[0];
			if (ARR_NDIM(fc_arr) != 1 ||
				fc_pronargs < 0 ||
				ARR_HASNULL(fc_arr) ||
				ARR_ELEMTYPE(fc_arr) != OIDOID)
				elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
			Assert(fc_pronargs >= fc_funcform->pronargs);
			fc_proargtypes = (Oid *) ARR_DATA_PTR(fc_arr);
		}
	}

	/* 我们有任何命名参数吗？ */
	foreach(fc_lc, fc_args)
	{
		Node	   *fc_arg = (Node *) lfirst(fc_lc);

		if (IsA(fc_arg, NamedArgExpr))
		{
			fc_has_named_args = true;
			break;
		}
	}

	/* 如果有，我们必须应用 reorder_function_arguments */
	if (fc_has_named_args)
	{
		fc_args = fc_reorder_function_arguments(fc_args, fc_pronargs, fc_func_tuple);
		/* 重新检查参数类型并在需要时添加转换 */
		fc_recheck_cast_function_args(fc_args, fc_result_type,
								   fc_proargtypes, fc_pronargs,
								   fc_func_tuple);
	}
	else if (list_length(fc_args) < fc_pronargs)
	{
		/* 没有命名参数，但我们似乎缺少一些默认值 */
		fc_args = add_function_defaults(fc_args, fc_pronargs, fc_func_tuple);
		/* 重新检查参数类型并在需要时添加转换 */
		fc_recheck_cast_function_args(fc_args, fc_result_type,
								   fc_proargtypes, fc_pronargs,
								   fc_func_tuple);
	}

	return fc_args;
}

/*
 * reorder_function_arguments：将命名表示法参数转换为位置参数
 *
 * 此函数还会根据需要插入默认参数值，因为没有它，无法形成真正有效的位置调用。
 */
static List * fc_reorder_function_arguments(List *fc_args, int fc_pronargs, HeapTuple fc_func_tuple)
{
	Form_pg_proc fc_funcform = (Form_pg_proc) GETSTRUCT(fc_func_tuple);
	int			fc_nargsprovided = list_length(fc_args);
	Node	   *fc_argarray[FUNC_MAX_ARGS];
	ListCell   *fc_lc;
	int			fc_i;

	Assert(fc_nargsprovided <= fc_pronargs);
	if (fc_pronargs < 0 || fc_pronargs > FUNC_MAX_ARGS)
		elog(ERROR, "too many function arguments");
	memset(fc_argarray, 0, fc_pronargs * sizeof(Node *));

	/* 将参数列表拆解为一个按 argnumber 索引的数组 */
	fc_i = 0;
	foreach(fc_lc, fc_args)
	{
		Node	   *fc_arg = (Node *) lfirst(fc_lc);

		if (!IsA(fc_arg, NamedArgExpr))
		{
			/* 位置参数，假设位于所有命名参数之前 */
			Assert(fc_argarray[fc_i] == NULL);
			fc_argarray[fc_i++] = fc_arg;
		}
		else
		{
			NamedArgExpr *fc_na = (NamedArgExpr *) fc_arg;

			Assert(fc_na->argnumber >= 0 && fc_na->argnumber < fc_pronargs);
			Assert(fc_argarray[fc_na->argnumber] == NULL);
			fc_argarray[fc_na->argnumber] = (Node *) fc_na->arg;
		}
	}

	/*
	 * 根据需要获取默认表达式，并在适当的位置插入到数组中
	 * （它们不一定是连续的或全部使用）
	 */
	if (fc_nargsprovided < fc_pronargs)
	{
		List	   *fc_defaults = fc_fetch_function_defaults(fc_func_tuple);

		fc_i = fc_pronargs - fc_funcform->pronargdefaults;
		foreach(fc_lc, fc_defaults)
		{
			if (fc_argarray[fc_i] == NULL)
				fc_argarray[fc_i] = (Node *) lfirst(fc_lc);
			fc_i++;
		}
	}

	/* 现在以正确的顺序重新构建 args 列表 */
	fc_args = NIL;
	for (fc_i = 0; fc_i < fc_pronargs; fc_i++)
	{
		Assert(fc_argarray[fc_i] != NULL);
		fc_args = lappend(fc_args, fc_argarray[fc_i]);
	}

	return fc_args;
}

/*
 * add_function_defaults: 添加缺失的函数参数及其默认值
 *
 * 仅在参数列表最初是位置参数时使用，因此我们知道只需在末尾添加默认值。
 */
static List * add_function_defaults(List *fc_args, int fc_pronargs, HeapTuple fc_func_tuple)
{
	int			fc_nargsprovided = list_length(fc_args);
	List	   *fc_defaults;
	int			fc_ndelete;

	/* 从 pg_proc 元组中获取所有默认表达式 */
	fc_defaults = fc_fetch_function_defaults(fc_func_tuple);

	/* 从列表中删除任何未使用的默认值 */
	fc_ndelete = fc_nargsprovided + list_length(fc_defaults) - fc_pronargs;
	if (fc_ndelete < 0)
		elog(ERROR, "not enough default arguments");
	if (fc_ndelete > 0)
		fc_defaults = list_delete_first_n(fc_defaults, fc_ndelete);

	/* 并形成组合参数列表，不修改输入列表 */
	return list_concat_copy(fc_args, fc_defaults);
}

/*
 * fetch_function_defaults: 获取函数的默认参数作为表达式列表
 */
static List * fc_fetch_function_defaults(HeapTuple fc_func_tuple)
{
	List	   *fc_defaults;
	Datum		fc_proargdefaults;
	bool		fc_isnull;
	char	   *fc_str;

	/* 这里的错误情况不应该发生，但还是要检查 */
	fc_proargdefaults = SysCacheGetAttr(PROCOID, fc_func_tuple,
									 Anum_pg_proc_proargdefaults,
									 &fc_isnull);
	if (fc_isnull)
		elog(ERROR, "not enough default arguments");
	fc_str = TextDatumGetCString(fc_proargdefaults);
	fc_defaults = castNode(List, stringToNode(fc_str));
	pfree(fc_str);
	return fc_defaults;
}

/*
 * recheck_cast_function_args: 在添加默认值后重新检查函数参数并进行类型转换
 *
 * 一些默认参数可能是多态的；因此，我们不能假设默认表达式已经具有正确的数据类型。
 * 我们必须重新解析多态，并像解析器那样进行强制转换。
 *
 * 如果没有多态参数，这应该是一个无操作，但为了确保我们还是这么做。
 *
 * 注意：如果需要任何强制转换，参数列表将就地修改；调用者应该已经复制了列表结构。
 */
static void fc_recheck_cast_function_args(List *fc_args, Oid fc_result_type,
						   Oid *fc_proargtypes, int fc_pronargs,
						   HeapTuple fc_func_tuple)
{
	Form_pg_proc fc_funcform = (Form_pg_proc) GETSTRUCT(fc_func_tuple);
	int			fc_nargs;
	Oid			fc_actual_arg_types[FUNC_MAX_ARGS];
	Oid			fc_declared_arg_types[FUNC_MAX_ARGS];
	Oid			fc_rettype;
	ListCell   *fc_lc;

	if (list_length(fc_args) > FUNC_MAX_ARGS)
		elog(ERROR, "too many function arguments");
	fc_nargs = 0;
	foreach(fc_lc, fc_args)
	{
		fc_actual_arg_types[fc_nargs++] = exprType((Node *) lfirst(fc_lc));
	}
	Assert(fc_nargs == fc_pronargs);
	memcpy(fc_declared_arg_types, fc_proargtypes, fc_pronargs * sizeof(Oid));
	fc_rettype = enforce_generic_type_consistency(fc_actual_arg_types,
											   fc_declared_arg_types,
											   fc_nargs,
											   fc_funcform->prorettype,
											   false);
	/* 让我们检查一下是否得到了与解析器相同的答案... */
	if (fc_rettype != fc_result_type)
		elog(ERROR, "function's resolved result type changed during planning");

	/* 执行参数的任何必要类型转换 */
	make_fn_arguments(NULL, fc_args, fc_actual_arg_types, fc_declared_arg_types);
}

/*
 * evaluate_function: 尝试预评估函数调用
 *
 * 如果函数是严格的，并且有任何常量空输入（只返回一个空常量），或者如果函数是不可变的并且有所有常量输入（调用它并将结果作为一个 Const 节点返回），我们可以这样做。在估算模式下，我们也愿意预评估稳定函数。
 *
 * 如果成功，将返回一个简化的表达式；如果无法简化函数，则返回 NULL。
 */
static Expr * fc_evaluate_function(Oid fc_funcid, Oid fc_result_type, int32 fc_result_typmod,
				  Oid fc_result_collid, Oid fc_input_collid, List *fc_args,
				  bool fc_funcvariadic,
				  HeapTuple fc_func_tuple,
				  eval_const_expressions_context *fc_context)
{
	Form_pg_proc fc_funcform = (Form_pg_proc) GETSTRUCT(fc_func_tuple);
	bool		fc_has_nonconst_input = false;
	bool		fc_has_null_input = false;
	ListCell   *fc_arg;
	FuncExpr   *fc_newexpr;

	/*
	 * 如果它返回一个集合，则无法简化。
	 */
	if (fc_funcform->proretset)
		return NULL;

	/*
	 * 如果它返回 RECORD，则无法简化。 直接的问题是，它将需要一个我们在这里无法提供的预期 tupdesc。
	 *
	 * 如果它有 OUT 参数，我们可以从这些参数构建一个预期的 tupdesc，但可能还有其他隐患。 特别是，如果函数返回 NULL，我们将生成一个没有剩余指示具体记录类型的空常量。 目前，最好的方法似乎是让函数调用不被简化。
	 */
	if (fc_funcform->prorettype == RECORDOID)
		return NULL;

	/*
	 * 检查常量输入，特别是常量空输入。
	 */
	foreach(fc_arg, fc_args)
	{
		if (IsA(lfirst(fc_arg), Const))
			fc_has_null_input |= ((Const *) lfirst(fc_arg))->constisnull;
		else
			fc_has_nonconst_input = true;
	}

	/*
	 * 如果函数是严格的并且有常量空输入，它将根本不会被调用，因此我们可以用一个空常量替换调用，即使还有其他不是常量的输入，即使函数在其他方面不是不可变的。
	 */
	if (fc_funcform->proisstrict && fc_has_null_input)
		return (Expr *) makeNullConst(fc_result_type, fc_result_typmod,
									  fc_result_collid);

	/*
	 * 否则，只有在所有输入都是常量的情况下才能简化。 （对于非严格函数，常量空输入与常量非空输入的处理相同。）
	 */
	if (fc_has_nonconst_input)
		return NULL;

	/*
	 * 通常我们只允许简化不可变函数。但出于估算目的，我们认为简化
	 * 仅仅是稳定的函数也是可以的；因为结果可能在规划时间与执行时间之间变化
	 * 的风险值得一试，而不完全无法估算这些值。
	 */
	if (fc_funcform->provolatile == PROVOLATILE_IMMUTABLE)
		 /* 可以 */ ;
	else if (fc_context->estimate && fc_funcform->provolatile == PROVOLATILE_STABLE)
		 /* 可以 */ ;
	else
		return NULL;

	/*
	 * 好吧，看来我们可以简化这个运算符/函数。
	 *
	 * 构建一个包含已简化参数的新 FuncExpr 节点。
	 */
	fc_newexpr = makeNode(FuncExpr);
	fc_newexpr->funcid = fc_funcid;
	fc_newexpr->funcresulttype = fc_result_type;
	fc_newexpr->funcretset = false;
	fc_newexpr->funcvariadic = fc_funcvariadic;
	fc_newexpr->funcformat = COERCE_EXPLICIT_CALL; /* 不重要 */
	fc_newexpr->funccollid = fc_result_collid;	/* 不重要 */
	fc_newexpr->inputcollid = fc_input_collid;
	fc_newexpr->args = fc_args;
	fc_newexpr->location = -1;

	return evaluate_expr((Expr *) fc_newexpr, fc_result_type, fc_result_typmod,
						 fc_result_collid);
}

/*
 * inline_function: 尝试将函数调用展开为内联
 *
 * 如果函数是一个充分简单的 SQL 语言函数（仅 "SELECT 表达式"），则我们可以将其内联，从而避免 SQL 函数每次调用时的高开销。此外，这可以在函数表达式中暴露常量折叠的机会。
 *
 * 然而，我们必须注意一些特殊情况。 直接或间接递归的函数会导致我们永远递归，因此我们会跟踪已经展开的函数，并不再重新展开它们。此外，如果参数在 SQL 函数体中使用超过一次，我们要求它不包含任何易变函数（易变函数可能会提供不一致的答案），也不会在计算时不合理地昂贵。 昂贵检查不仅防止我们在运行时对昂贵参数进行多次评估，而且是控制由于反复内联而导致表达式增长的安全值。
 *
 * 我们还必须小心通过内联改变函数的易变性或严格性状态。
 *
 * 此外，目前我们无法内联返回 RECORD 的函数。 这是因为它会丢弃信息，例如 OUT 参数声明。
 *
 * 此外，参数列表中的上下文相关表达式节点也是麻烦。
 *
 * 如果成功，将返回一个简化的表达式；如果无法简化函数，则返回 NULL。
 */
static Expr * fc_inline_function(Oid fc_funcid, Oid fc_result_type, Oid fc_result_collid,
				Oid fc_input_collid, List *fc_args,
				bool fc_funcvariadic,
				HeapTuple fc_func_tuple,
				eval_const_expressions_context *fc_context)
{
	Form_pg_proc fc_funcform = (Form_pg_proc) GETSTRUCT(fc_func_tuple);
	char	   *fc_src;
	Datum		fc_tmp;
	bool		fc_isNull;
	MemoryContext fc_oldcxt;
	MemoryContext fc_mycxt;
	inline_error_callback_arg fc_callback_arg;
	ErrorContextCallback fc_sqlerrcontext;
	FuncExpr   *fc_fexpr;
	SQLFunctionParseInfoPtr fc_pinfo;
	TupleDesc	fc_rettupdesc;
	ParseState *fc_pstate;
	List	   *fc_raw_parsetree_list;
	List	   *fc_querytree_list;
	Query	   *fc_querytree;
	Node	   *fc_newexpr;
	int		   *fc_usecounts;
	ListCell   *fc_arg;
	int			fc_i;

	/*
	 * 如果函数不是 SQL 语言或具有其他严重限制属性，则放弃。
	 * （prokind 和 nargs 检查仅仅是出于谨慎。）
	 */
	if (fc_funcform->prolang != SQLlanguageId ||
		fc_funcform->prokind != PROKIND_FUNCTION ||
		fc_funcform->prosecdef ||
		fc_funcform->proretset ||
		fc_funcform->prorettype == RECORDOID ||
		!heap_attisnull(fc_func_tuple, Anum_pg_proc_proconfig, NULL) ||
		fc_funcform->pronargs != list_length(fc_args))
		return NULL;

	/* 检查递归函数，如果是则放弃尝试展开 */
	if (list_member_oid(fc_context->active_fns, fc_funcid))
		return NULL;

	/* 检查调用函数的权限（如果没有，稍后会失败） */
	if (pg_proc_aclcheck(fc_funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
		return NULL;

	/* 检查插件是否希望挂钩函数的进入/退出 */
	if (FmgrHookIsNeeded(fc_funcid))
		return NULL;

	
/*
	 * 创建一个临时内存上下文，以便我们不会泄漏解析可能创建的所有内容。
	 */
	fc_mycxt = AllocSetContextCreate(CurrentMemoryContext,
								  "inline_function",
								  ALLOCSET_DEFAULT_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_mycxt);

	/*
	 * 我们需要一个包含已经简化参数的虚拟 FuncExpr 节点。
	 * （在某些情况下我们并不真正需要它，但构建它的成本足够低，避免它并不值得。）
	 */
	fc_fexpr = makeNode(FuncExpr);
	fc_fexpr->funcid = fc_funcid;
	fc_fexpr->funcresulttype = fc_result_type;
	fc_fexpr->funcretset = false;
	fc_fexpr->funcvariadic = fc_funcvariadic;
	fc_fexpr->funcformat = COERCE_EXPLICIT_CALL;	/* 不重要 */
	fc_fexpr->funccollid = fc_result_collid;	/* 不重要 */
	fc_fexpr->inputcollid = fc_input_collid;
	fc_fexpr->args = fc_args;
	fc_fexpr->location = -1;

	/* 获取函数体 */
	fc_tmp = SysCacheGetAttr(PROCOID,
						  fc_func_tuple,
						  Anum_pg_proc_prosrc,
						  &fc_isNull);
	if (fc_isNull)
		elog(ERROR, "null prosrc for function %u", fc_funcid);
	fc_src = TextDatumGetCString(fc_tmp);

	/*
	 * 为 ereport() 设置错误 traceback 支持。
	 * 这样我们可以确定错误信息来源于哪个函数。
	 */
	fc_callback_arg.proname = NameStr(fc_funcform->proname);
	fc_callback_arg.prosrc = fc_src;

	fc_sqlerrcontext.callback = fc_sql_inline_error_callback;
	fc_sqlerrcontext.arg = (void *) &fc_callback_arg;
	fc_sqlerrcontext.previous = error_context_stack;
	error_context_stack = &fc_sqlerrcontext;

	/* 如果我们有 prosqlbody，则关注该部分而不是 prosrc */
	fc_tmp = SysCacheGetAttr(PROCOID,
						  fc_func_tuple,
						  Anum_pg_proc_prosqlbody,
						  &fc_isNull);
	if (!fc_isNull)
	{
		Node	   *fc_n;
		List	   *fc_querytree_list;

		fc_n = stringToNode(TextDatumGetCString(fc_tmp));
		if (IsA(fc_n, List))
			fc_querytree_list = linitial_node(List, castNode(List, fc_n));
		else
			fc_querytree_list = list_make1(fc_n);
		if (list_length(fc_querytree_list) != 1)
			goto fail;
		fc_querytree = linitial(fc_querytree_list);

		/*
		 * 因为我们将在下面坚持要求 querytree 具有一个空的 rtable
		 * 和没有 sublinks，所以它不能有任何需要锁定或重写的关系引用。
		 * 因此，我们可以省略这些步骤。
		 */
	}
	else
	{
		/* 设置以处理函数体解析期间的参数。 */
		fc_pinfo = prepare_sql_fn_parse_info(fc_func_tuple,
										  (Node *) fc_fexpr,
										  fc_input_collid);

		/*
		 * 我们只进行解析和解析分析，不进行重写，
		 * 因为重写不会影响无表的 SELECT 仅查询，这正是我们所关心的。
		 * 此外，一旦我们检测到函数体中有多个命令，就可以停止处理。
		 */
		fc_raw_parsetree_list = pg_parse_query(fc_src);
		if (list_length(fc_raw_parsetree_list) != 1)
			goto fail;

		fc_pstate = make_parsestate(NULL);
		fc_pstate->p_sourcetext = fc_src;
		sql_fn_parser_setup(fc_pstate, fc_pinfo);

		fc_querytree = transformTopLevelStmt(fc_pstate, linitial(fc_raw_parsetree_list));

		free_parsestate(fc_pstate);
	}

	/*
	 * 单个命令必须是一个简单的“SELECT 表达式”。
	 *
	 * 注意：如果您更改了与此相关的测试，请参阅 plpgsql 的
	 * exec_simple_check_plan()。它通常需要对什么是“简单表达式”有相同的理解，
	 * 这样内联一个先前没有内联的函数不会改变 plpgsql 的结论。
	 */
	if (!IsA(fc_querytree, Query) ||
		fc_querytree->commandType != CMD_SELECT ||
		fc_querytree->hasAggs ||
		fc_querytree->hasWindowFuncs ||
		fc_querytree->hasTargetSRFs ||
		fc_querytree->hasSubLinks ||
		fc_querytree->cteList ||
		fc_querytree->rtable ||
		fc_querytree->jointree->fromlist ||
		fc_querytree->jointree->quals ||
		fc_querytree->groupClause ||
		fc_querytree->groupingSets ||
		fc_querytree->havingQual ||
		fc_querytree->windowClause ||
		fc_querytree->distinctClause ||
		fc_querytree->sortClause ||
		fc_querytree->limitOffset ||
		fc_querytree->limitCount ||
		fc_querytree->setOperations ||
		list_length(fc_querytree->targetList) != 1)
		goto fail;

	/* 如果函数结果是复合类型，解析它 */
	(void) get_expr_result_type((Node *) fc_fexpr,
								NULL,
								&fc_rettupdesc);

	/*
	 * 确保该函数（仍然）返回其声明的内容。 如果错误，将引发错误，
	 * 但这没关系，因为该函数在运行时无论如何都会失败。
	 * 注意，check_sql_fn_retval 也会在需要时插入一个强制转换，
	 * 以使 tlist 表达式与函数声明的类型匹配。
	 *
	 * 注意：我们不在进行重写的情况下尝试；这可能并不重要，
	 * 但我们要小心。
	 */
	fc_querytree_list = list_make1(fc_querytree);
	if (check_sql_fn_retval_ext(list_make1(fc_querytree_list),
								fc_result_type, fc_rettupdesc,
								fc_funcform->prokind,
								false, NULL))
		goto fail;				/* 拒绝整个元组结果的情况 */

	/*
	 * 根据上述测试，check_sql_fn_retval 不应该决定插入一个投影步骤，
	 * 但让我们确保一下。
	 */
	if (fc_querytree != linitial(fc_querytree_list))
		goto fail;

	/* 现在我们可以获取 tlist 表达式 */
	fc_newexpr = (Node *) ((TargetEntry *) linitial(fc_querytree->targetList))->expr;

	/*
	 * 如果 SQL 函数返回 VOID，只有在它是一个返回 VOID 的表达式的
	 * SELECT 时，我们才能将其内联（即，它只是重定向到另一个返回
	 * VOID 的函数）。 在所有非 VOID 返回的情况下，check_sql_fn_retval
	 * 应确保 newexpr 返回函数的声明结果类型，因此这个测试不该
	 * 失败；但如果失败，我们也可以优雅地处理。
	 */
	if (exprType(fc_newexpr) != fc_result_type)
		goto fail;

	/*
	 * 对表达式进行额外的有效性检查。 它的波动性不能比周围的
	 * 函数更大（这是为了避免破坏涉及伪装函数为不变性但实际上不是的
	 * 黑客）。 如果周围的函数被声明为严格的，则表达式必须仅包含
	 * 严格构造，并且必须使用所有函数参数（这有点过火，但精确分析
	 * 很难）。
	 */
	if (fc_funcform->provolatile == PROVOLATILE_IMMUTABLE &&
		contain_mutable_functions(fc_newexpr))
		goto fail;
	else if (fc_funcform->provolatile == PROVOLATILE_STABLE &&
			 contain_volatile_functions(fc_newexpr))
		goto fail;

	if (fc_funcform->proisstrict &&
		contain_nonstrict_functions(fc_newexpr))
		goto fail;

	/*
	 * 如果任何参数表达式包含上下文依赖节点，我们不能进行内联，
	 * 以免将此类节点放入错误的上下文中。
	 */
	if (fc_contain_context_dependent_node((Node *) fc_args))
		goto fail;

	/*
	 * 我们可能能够做到；还有对参数使用的检查，
	 * 但这些检查最好与输入的实际替换结合进行。
	 * 所以开始构建用输入替换的表达式。
	 */
	fc_usecounts = (int *) palloc0(fc_funcform->pronargs * sizeof(int));
	fc_newexpr = fc_substitute_actual_parameters(fc_newexpr, fc_funcform->pronargs,
										   fc_args, fc_usecounts);

	/* 现在检查参数使用情况 */
	fc_i = 0;
	foreach(fc_arg, fc_args)
	{
		Node	   *fc_param = lfirst(fc_arg);

		if (fc_usecounts[fc_i] == 0)
		{
			/* 参数根本没有被使用：如果函数是严格的，这不好 */
			if (fc_funcform->proisstrict)
				goto fail;
		}
		else if (fc_usecounts[fc_i] != 1)
		{
			/* 参数被多次使用：如果昂贵或波动，这不好 */
			QualCost	fc_eval_cost;

			/*
			 * 我们将“昂贵”定义为“包含任何子计划或超过 10
			 * 个操作符”。 注意，子计划搜索必须显式进行，
			 * 因为 cost_qual_eval() 会在未计划的子选择上出错。
			 */
			if (contain_subplans(fc_param))
				goto fail;
			cost_qual_eval(&fc_eval_cost, list_make1(fc_param), NULL);
			if (fc_eval_cost.startup + fc_eval_cost.per_tuple >
				10 * cpu_operator_cost)
				goto fail;

			/*
			 * 最后检查波动性，因为这比上述测试更昂贵
			 */
			if (contain_volatile_functions(fc_param))
				goto fail;
		}
		fc_i++;
	}

	/*
	 * 呼，——我们可以进行替换。 从临时内存上下文中复制修改后的
	 * 表达式，并进行清理。
	 */
	MemoryContextSwitchTo(fc_oldcxt);

	fc_newexpr = copyObject(fc_newexpr);

	MemoryContextDelete(fc_mycxt);

	/*
	 * 如果结果是可排序的类型，强制结果显示正确的排序方式。
	 * 在大多数情况下，这并不重要，但可能会直接将函数结果用作
	 * 排序键或其他需要 exprCollation() 说明真实情况的地方。
	 */
	if (OidIsValid(fc_result_collid))
	{
		Oid			fc_exprcoll = exprCollation(fc_newexpr);

		if (OidIsValid(fc_exprcoll) && fc_exprcoll != fc_result_collid)
		{
			CollateExpr *fc_newnode = makeNode(CollateExpr);

			fc_newnode->arg = (Expr *) fc_newexpr;
			fc_newnode->collOid = fc_result_collid;
			fc_newnode->location = -1;

			fc_newexpr = (Node *) fc_newnode;
		}
	}

	/*
	 * 由于现在计划树中没有该函数的痕迹，我们必须显式记录
	 * 该计划对函数的依赖。
	 */
	if (fc_context->root)
		record_plan_function_dependency(fc_context->root, fc_funcid);

	/*
	 * 递归尝试简化修改后的表达式。 在这里我们必须将当前函数添加
	 * 到活动函数的上下文列表中。
	 */
	fc_context->active_fns = lappend_oid(fc_context->active_fns, fc_funcid);
	fc_newexpr = fc_eval_const_expressions_mutator(fc_newexpr, fc_context);
	fc_context->active_fns = list_delete_last(fc_context->active_fns);

	error_context_stack = fc_sqlerrcontext.previous;

	return (Expr *) fc_newexpr;

	/* 如果函数不可内联：释放临时内存并返回 NULL */
fail:
	MemoryContextSwitchTo(fc_oldcxt);
	MemoryContextDelete(fc_mycxt);
	error_context_stack = fc_sqlerrcontext.previous;

	return NULL;
}

/*
 * 用适当的实际参数替换 Param 节点
 */
static Node * fc_substitute_actual_parameters(Node *fc_expr, int fc_nargs, List *fc_args,
							 int *fc_usecounts)
{
	substitute_actual_parameters_context fc_context;

	fc_context.nargs = fc_nargs;
	fc_context.args = fc_args;
	fc_context.usecounts = fc_usecounts;

	return fc_substitute_actual_parameters_mutator(fc_expr, &fc_context);
}

static Node * fc_substitute_actual_parameters_mutator(Node *fc_node,
									 substitute_actual_parameters_context *fc_context)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Param))
	{
		Param	   *fc_param = (Param *) fc_node;

		if (fc_param->paramkind != PARAM_EXTERN)
			elog(ERROR, "unexpected paramkind: %d", (int) fc_param->paramkind);
		if (fc_param->paramid <= 0 || fc_param->paramid > fc_context->nargs)
			elog(ERROR, "invalid paramid: %d", fc_param->paramid);

		/* 计数参数使用情况 */
		fc_context->usecounts[fc_param->paramid - 1]++;

		/* 选择适当的实际参数并用其替换 Param */
		/* 我们现在不需要复制（稍后会处理） */
		return list_nth(fc_context->args, fc_param->paramid - 1);
	}
	return expression_tree_mutator(fc_node, fc_substitute_actual_parameters_mutator,
								   (void *) fc_context);
}

/*
 * 错误上下文回调，以便让我们提供调用栈回溯
 */
static void fc_sql_inline_error_callback(void *fc_arg)
{
	inline_error_callback_arg *fc_callback_arg = (inline_error_callback_arg *) fc_arg;
	int			fc_syntaxerrposition;

	/* 如果是语法错误，则转换为内部语法错误报告 */
	fc_syntaxerrposition = geterrposition();
	if (fc_syntaxerrposition > 0)
	{
		errposition(0);
		internalerrposition(fc_syntaxerrposition);
		internalerrquery(fc_callback_arg->prosrc);
	}

	errcontext("SQL function \"%s\" during inlining", fc_callback_arg->proname);
}

/*
 * evaluate_expr：预评估常量表达式
 *
 * 我们使用执行器的例程 ExecEvalExpr() 来避免代码重复，并
 * 确保我们获得与执行器将获得的相同结果。
 */
Expr * evaluate_expr(Expr *fc_expr, Oid fc_result_type, int32 fc_result_typmod,
			  Oid fc_result_collation)
{
	EState	   *fc_estate;
	ExprState  *fc_exprstate;
	MemoryContext fc_oldcontext;
	Datum		fc_const_val;
	bool		fc_const_is_null;
	int16		fc_resultTypLen;
	bool		fc_resultTypByVal;

	/*
	 * 要使用执行器，我们需要一个 EState。
	 */
	fc_estate = CreateExecutorState();

	/* 我们可以使用状态机的工作上下文来避免内存泄漏。 */
	fc_oldcontext = MemoryContextSwitchTo(fc_estate->es_query_cxt);

	/* 确保任何 opfuncids 都已填充。 */
	fix_opfuncids((Node *) fc_expr);

	/*
	 * 为执行准备 expr。（注意：我们不能使用 ExecPrepareExpr，
	 * 因为这会导致递归调用 eval_const_expressions。）
	 */
	fc_exprstate = ExecInitExpr(fc_expr, NULL);

	/*
	 * 然后进行评估。
	 *
	 * 使用默认的 econtext 是可以的，因为在这种情况下所用的
	 * ExecEvalExpr() 代码不会使用 econtext。 这可能看起来像是
	 * 幸运，但也并非不合理——常量表达式根据定义不依赖于上下文， 
	 * n'est ce pas？
	 */
	fc_const_val = ExecEvalExprSwitchContext(fc_exprstate,
										  GetPerTupleExprContext(fc_estate),
										  &fc_const_is_null);

	/* 获取有关结果数据类型所需的信息 */
	get_typlenbyval(fc_result_type, &fc_resultTypLen, &fc_resultTypByVal);

	/* 返回到外层内存上下文 */
	MemoryContextSwitchTo(fc_oldcontext);

	/*
	 * 必须将结果复制到表达式 eval 使用的子上下文外。
	 *
	 * 此外，如果是 varlena，则强制解冻。这可以保护我们免于
	 * 将 TOAST 指针存储到可能超出引用数据生命周期的计划中。
	 * （makeConst 无论如何会处理解冻，但这里值得多写几行，以便
	 * 我们可以在一步中进行复制和解冻。）
	 */
	if (!fc_const_is_null)
	{
		if (fc_resultTypLen == -1)
			fc_const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(fc_const_val));
		else
			fc_const_val = datumCopy(fc_const_val, fc_resultTypByVal, fc_resultTypLen);
	}

	/* 释放我们刚创建的所有垃圾 */
	FreeExecutorState(fc_estate);

	/*
	 * 创建常量结果节点。
	 */
	return (Expr *) makeConst(fc_result_type, fc_result_typmod, fc_result_collation,
							  fc_resultTypLen,
							  fc_const_val, fc_const_is_null,
							  fc_resultTypByVal);
}


/*
 * inline_set_returning_function
 *		尝试在 FROM 子句中“内联”一个集合返回函数。
 *
 * “rte” 是 RTE_FUNCTION 范围表条目。如果它表示一个
 * 可以安全内联的集合返回 SQL 函数的调用，则展开该函数
 * 并返回替代的查询结构。否则，返回 NULL。
 *
 * 我们假设 RTE 的表达式已经通过了
 * eval_const_expressions()，这将处理
 * 默认参数和命名参数表示法等。
 *
 * 这与 inline_function() 有许多相似之处，但那是
 * 针对非集合返回的情况，并且有足够的差异来
 * 证明需要单独的函数。
 */
Query * inline_set_returning_function(PlannerInfo *fc_root, RangeTblEntry *fc_rte)
{
	RangeTblFunction *fc_rtfunc;
	FuncExpr   *fc_fexpr;
	Oid			fc_func_oid;
	HeapTuple	fc_func_tuple;
	Form_pg_proc fc_funcform;
	char	   *fc_src;
	Datum		fc_tmp;
	bool		fc_isNull;
	MemoryContext fc_oldcxt;
	MemoryContext fc_mycxt;
	inline_error_callback_arg fc_callback_arg;
	ErrorContextCallback fc_sqlerrcontext;
	SQLFunctionParseInfoPtr fc_pinfo;
	TypeFuncClass fc_functypclass;
	TupleDesc	fc_rettupdesc;
	List	   *fc_raw_parsetree_list;
	List	   *fc_querytree_list;
	Query	   *fc_querytree;

	Assert(fc_rte->rtekind == RTE_FUNCTION);

	/*
	 * 对于 SQL SRF 在其自身的 FROM 子句中引用自己并没有太大意义，
	 * 因为这必定会在运行时导致无限递归。
	 * 这也会导致此代码递归，因此检查栈溢出。
	 * （没有必要再做更多检查。）
	 */
	check_stack_depth();

	/* 如果 RTE 有 ORDINALITY 则失败 - 我们在这里不实现。 */
	if (fc_rte->funcordinality)
		return NULL;

	/* 如果 RTE 不是单个简单的 FuncExpr 则失败 */
	if (list_length(fc_rte->functions) != 1)
		return NULL;
	fc_rtfunc = (RangeTblFunction *) linitial(fc_rte->functions);

	if (!IsA(fc_rtfunc->funcexpr, FuncExpr))
		return NULL;
	fc_fexpr = (FuncExpr *) fc_rtfunc->funcexpr;

	fc_func_oid = fc_fexpr->funcid;

	/*
	 * 函数必须声明为返回一个集合，否则内联会
	 * 改变结果，如果包含的 SELECT 没有返回精确的单行。
	 */
	if (!fc_fexpr->funcretset)
		return NULL;

	/*
	 * 如果参数包含任何易变函数或子选择，则拒绝内联。
	 * 拒绝易变函数是因为内联可能导致参数被多次评估，
	 * 危害行为的一致性。部分由于实现原因，拒绝子选择
	 * （将它们向下推可能会改变它们的行为），
	 * 部分原因是它们可能很昂贵，因此多次评估会很糟糕。
	 */
	if (contain_volatile_functions((Node *) fc_fexpr->args) ||
		contain_subplans((Node *) fc_fexpr->args))
		return NULL;

	/* 检查调用函数的权限（如果没有，稍后会失败） */
	if (pg_proc_aclcheck(fc_func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
		return NULL;

	/* 检查插件是否希望挂钩函数的进入/退出 */
	if (FmgrHookIsNeeded(fc_func_oid))
		return NULL;

	/*
	 * 好吧，让我们看看函数的 pg_proc 条目。
	 */
	fc_func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_func_oid));
	if (!HeapTupleIsValid(fc_func_tuple))
		elog(ERROR, "cache lookup failed for function %u", fc_func_oid);
	fc_funcform = (Form_pg_proc) GETSTRUCT(fc_func_tuple);

	/*
	 * 如果该函数不是 SQL 语言或具有其他阻止特性的属性，
	 * 则抛弃它。特别是它不能声明为 STRICT，
	 * 因为我们无法强制执行这一点。还必须没有 VOLATILE，
	 * 因为这应该导致它在自己的快照下执行，而不是共享调用查询的快照。
	 * 我们还不允许返回 SETOF VOID，因为内联会导致暴露函数最后一个
	 * SELECT 的实际结果，这在这种情况下是不应发生的。
	 * （重新检查 prokind、proretset 和 pronargs 只是偏执。）
	 */
	if (fc_funcform->prolang != SQLlanguageId ||
		fc_funcform->prokind != PROKIND_FUNCTION ||
		fc_funcform->proisstrict ||
		fc_funcform->provolatile == PROVOLATILE_VOLATILE ||
		fc_funcform->prorettype == VOIDOID ||
		fc_funcform->prosecdef ||
		!fc_funcform->proretset ||
		list_length(fc_fexpr->args) != fc_funcform->pronargs ||
		!heap_attisnull(fc_func_tuple, Anum_pg_proc_proconfig, NULL))
	{
		ReleaseSysCache(fc_func_tuple);
		return NULL;
	}

	
/*
	 * 创建一个临时内存上下文，以便我们不会泄漏解析可能创建的所有内容。
	 */
	fc_mycxt = AllocSetContextCreate(CurrentMemoryContext,
								  "inline_set_returning_function",
								  ALLOCSET_DEFAULT_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_mycxt);

	/* 获取函数体 */
	fc_tmp = SysCacheGetAttr(PROCOID,
						  fc_func_tuple,
						  Anum_pg_proc_prosrc,
						  &fc_isNull);
	if (fc_isNull)
		elog(ERROR, "null prosrc for function %u", fc_func_oid);
	fc_src = TextDatumGetCString(fc_tmp);

	/*
	 * 为 ereport() 设置错误 traceback 支持。
	 * 这样我们可以确定错误信息来源于哪个函数。
	 */
	fc_callback_arg.proname = NameStr(fc_funcform->proname);
	fc_callback_arg.prosrc = fc_src;

	fc_sqlerrcontext.callback = fc_sql_inline_error_callback;
	fc_sqlerrcontext.arg = (void *) &fc_callback_arg;
	fc_sqlerrcontext.previous = error_context_stack;
	error_context_stack = &fc_sqlerrcontext;

	/* 如果我们有 prosqlbody，则关注该部分而不是 prosrc */
	fc_tmp = SysCacheGetAttr(PROCOID,
						  fc_func_tuple,
						  Anum_pg_proc_prosqlbody,
						  &fc_isNull);
	if (!fc_isNull)
	{
		Node	   *fc_n;

		fc_n = stringToNode(TextDatumGetCString(fc_tmp));
		if (IsA(fc_n, List))
			fc_querytree_list = linitial_node(List, castNode(List, fc_n));
		else
			fc_querytree_list = list_make1(fc_n);
		if (list_length(fc_querytree_list) != 1)
			goto fail;
		fc_querytree = linitial(fc_querytree_list);

		/* 获取必要的锁，然后应用重写器。 */
		AcquireRewriteLocks(fc_querytree, true, false);
		fc_querytree_list = pg_rewrite_query(fc_querytree);
		if (list_length(fc_querytree_list) != 1)
			goto fail;
		fc_querytree = linitial(fc_querytree_list);
	}
	else
	{
		/*
		 * 在解析函数体时设置好处理参数的准备工作。
		 * 我们可以使用刚刚创建的 FuncExpr 作为
		 * prepare_sql_fn_parse_info 的输入。
		 */
		fc_pinfo = prepare_sql_fn_parse_info(fc_func_tuple,
										  (Node *) fc_fexpr,
										  fc_fexpr->inputcollid);

		/*
		 * 解析、分析和重写（与 inline_function() 不同，
		 * 我们在这里不能跳过重写）。不过只要找到超过一个
		 * 查询就可以失败。
		 */
		fc_raw_parsetree_list = pg_parse_query(fc_src);
		if (list_length(fc_raw_parsetree_list) != 1)
			goto fail;

		fc_querytree_list = pg_analyze_and_rewrite_withcb(linitial(fc_raw_parsetree_list),
													   fc_src,
													   (ParserSetupHook) sql_fn_parser_setup,
													   fc_pinfo, NULL);
		if (list_length(fc_querytree_list) != 1)
			goto fail;
		fc_querytree = linitial(fc_querytree_list);
	}

	/*
	 * 如果是复合类型，还要解析实际的函数结果 tupdesc。
	 * 如果我们有 coldeflist，则相信它；否则使用 get_expr_result_type。
	 * （这个逻辑应该与 ExecInitFunctionScan 匹配。）
	 */
	if (fc_rtfunc->funccolnames != NIL)
	{
		fc_functypclass = TYPEFUNC_RECORD;
		fc_rettupdesc = BuildDescFromLists(fc_rtfunc->funccolnames,
										fc_rtfunc->funccoltypes,
										fc_rtfunc->funccoltypmods,
										fc_rtfunc->funccolcollations);
	}
	else
		fc_functypclass = get_expr_result_type((Node *) fc_fexpr, NULL, &fc_rettupdesc);

	/*
	 * 单一命令必须是普通的 SELECT。
	 */
	if (!IsA(fc_querytree, Query) ||
		fc_querytree->commandType != CMD_SELECT)
		goto fail;

	/*
	 * 确保函数（仍然）返回其声明的内容。
	 * 如果错误，将引发错误，但没关系，因为函数在运行时也会失败。
	 * 请注意，check_sql_fn_retval 还会根据需要插入强制转换，
	 * 以使 tlist 表达式与函数的声明类型匹配。我们还要求
	 * 它为 rettupdesc 中的任何丢失列插入虚拟 NULL 列，
	 * 以便修改后的 tlist 的元素与属性编号匹配。
	 *
	 * 如果函数返回复合类型，除非检查显示返回整个元组结果，
	 * 否则不要内联；否则它返回的是单个复合列，这不是我们需要的。
	 */
	if (!check_sql_fn_retval_ext(list_make1(fc_querytree_list),
								 fc_fexpr->funcresulttype, fc_rettupdesc,
								 fc_funcform->prokind,
								 true, NULL) &&
		(fc_functypclass == TYPEFUNC_COMPOSITE ||
		 fc_functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
		 fc_functypclass == TYPEFUNC_RECORD))
		goto fail;				/* 拒绝非整个元组结果的情况 */

	/*
	 * check_sql_fn_retval 可能已经插入了投影步骤，但这没关系；
	 * 只需确保我们使用上层查询。
	 */
	fc_querytree = linitial_node(Query, fc_querytree_list);

	/*
	 * 看起来不错---将参数替换到查询中。
	 */
	fc_querytree = fc_substitute_actual_srf_parameters(fc_querytree,
												 fc_funcform->pronargs,
												 fc_fexpr->args);

	/*
	 * 将修改后的查询复制到临时内存上下文外，并清理。
	 */
	MemoryContextSwitchTo(fc_oldcxt);

	fc_querytree = copyObject(fc_querytree);

	MemoryContextDelete(fc_mycxt);
	error_context_stack = fc_sqlerrcontext.previous;
	ReleaseSysCache(fc_func_tuple);

	/*
	 * 我们这里不需要修复排序规则，因为上层查询已经
	 * 被解析，即 RTE 中的排序规则是重要的。
	 */

	/*
	 * 由于现在计划树中没有该函数的痕迹，我们必须显式记录
	 * 该计划对函数的依赖。
	 */
	record_plan_function_dependency(fc_root, fc_func_oid);

	/*
	 * 我们还必须注意，如果插入的查询由于 RLS 条件而增加了
	 * 对调用角色的依赖。
	 */
	if (fc_querytree->hasRowSecurity)
		fc_root->glob->dependsOnRole = true;

	return fc_querytree;

	/* 如果函数不可内联：释放临时内存并返回 NULL */
fail:
	MemoryContextSwitchTo(fc_oldcxt);
	MemoryContextDelete(fc_mycxt);
	error_context_stack = fc_sqlerrcontext.previous;
	ReleaseSysCache(fc_func_tuple);

	return NULL;
}


/*
 * 替换参数节点为适当的实际参数
 *
 * 这与substitute_actual_parameters()的不同之处刚好足够，
 * 因此需要自己的代码。
 */
static Query * fc_substitute_actual_srf_parameters(Query *fc_expr, int fc_nargs, List *fc_args)
{
	substitute_actual_srf_parameters_context fc_context;

	fc_context.nargs = fc_nargs;
	fc_context.args = fc_args;
	fc_context.sublevels_up = 1;

	return query_tree_mutator(fc_expr,
							  fc_substitute_actual_srf_parameters_mutator,
							  &fc_context,
							  0);
}

static Node * fc_substitute_actual_srf_parameters_mutator(Node *fc_node,
										 substitute_actual_srf_parameters_context *fc_context)
{
	Node	   *fc_result;

	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Query))
	{
		fc_context->sublevels_up++;
		fc_result = (Node *) query_tree_mutator((Query *) fc_node,
											 fc_substitute_actual_srf_parameters_mutator,
											 (void *) fc_context,
											 0);
		fc_context->sublevels_up--;
		return fc_result;
	}
	if (IsA(fc_node, Param))
	{
		Param	   *fc_param = (Param *) fc_node;

		if (fc_param->paramkind == PARAM_EXTERN)
		{
			if (fc_param->paramid <= 0 || fc_param->paramid > fc_context->nargs)
				elog(ERROR, "invalid paramid: %d", fc_param->paramid);

			/*
			 * 由于参数被插入到子查询中，我们必须
			 * 调整级别。
			 */
			fc_result = copyObject(list_nth(fc_context->args, fc_param->paramid - 1));
			IncrementVarSublevelsUp(fc_result, fc_context->sublevels_up, 0);
			return fc_result;
		}
	}
	return expression_tree_mutator(fc_node,
								   fc_substitute_actual_srf_parameters_mutator,
								   (void *) fc_context);
}

/*
 * pull_paramids
 *		返回一个Bitmapset，其中包含'expr'中所有参数的paramids。
 */
Bitmapset * pull_paramids(Expr *fc_expr)
{
	Bitmapset  *fc_result = NULL;

	(void) fc_pull_paramids_walker((Node *) fc_expr, &fc_result);

	return fc_result;
}

static bool fc_pull_paramids_walker(Node *fc_node, Bitmapset **fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Param))
	{
		Param	   *fc_param = (Param *) fc_node;

		*fc_context = bms_add_member(*fc_context, fc_param->paramid);
		return false;
	}
	return expression_tree_walker(fc_node, fc_pull_paramids_walker,
								  (void *) fc_context);
}
